oxirs_core/query/
functions.rs

1//! SPARQL 1.2 built-in functions and extensions
2//!
3//! This module implements the extended function library for SPARQL 1.2,
4//! including new string functions, math functions, and advanced features.
5
6use crate::model::{Literal, NamedNode, Term};
7// use crate::query::algebra::Expression; // For future expression evaluation
8use crate::OxirsError;
9use chrono::{DateTime, Datelike, Timelike, Utc};
10use regex::Regex;
11use std::collections::HashMap;
12use std::sync::Arc;
13
14/// SPARQL function registry
15pub struct FunctionRegistry {
16    /// Built-in functions
17    functions: HashMap<String, FunctionImpl>,
18    /// Custom extension functions
19    extensions: HashMap<String, Arc<dyn CustomFunction>>,
20}
21
22/// Function implementation
23pub enum FunctionImpl {
24    /// Native Rust implementation
25    Native(NativeFunction),
26    /// JavaScript implementation (for extensibility)
27    JavaScript(String),
28    /// WASM module
29    Wasm(Vec<u8>),
30}
31
32/// Native function pointer
33pub type NativeFunction = Arc<dyn Fn(&[Term]) -> Result<Term, OxirsError> + Send + Sync>;
34
35/// Custom function trait
36pub trait CustomFunction: Send + Sync {
37    /// Execute the function
38    fn execute(&self, args: &[Term]) -> Result<Term, OxirsError>;
39
40    /// Get function metadata
41    fn metadata(&self) -> FunctionMetadata;
42}
43
44/// Function metadata
45#[derive(Debug, Clone)]
46pub struct FunctionMetadata {
47    /// Function name
48    pub name: String,
49    /// Description
50    pub description: String,
51    /// Minimum arguments
52    pub min_args: usize,
53    /// Maximum arguments (None = unlimited)
54    pub max_args: Option<usize>,
55    /// Argument types
56    pub arg_types: Vec<ArgumentType>,
57    /// Return type
58    pub return_type: ReturnType,
59}
60
61/// Argument type specification
62#[derive(Debug, Clone)]
63pub enum ArgumentType {
64    /// Any RDF term
65    Any,
66    /// String literal
67    String,
68    /// Numeric literal
69    Numeric,
70    /// Boolean literal
71    Boolean,
72    /// Date/time literal
73    DateTime,
74    /// IRI
75    IRI,
76    /// Specific datatype
77    Datatype(String),
78}
79
80/// Return type specification
81#[derive(Debug, Clone)]
82pub enum ReturnType {
83    /// Same as input
84    SameAsInput,
85    /// Specific type
86    Fixed(ArgumentType),
87    /// Dynamic based on input
88    Dynamic,
89}
90
91impl Default for FunctionRegistry {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl FunctionRegistry {
98    /// Create new function registry with SPARQL 1.2 built-ins
99    pub fn new() -> Self {
100        let mut registry = FunctionRegistry {
101            functions: HashMap::new(),
102            extensions: HashMap::new(),
103        };
104
105        registry.register_sparql_12_functions();
106        registry
107    }
108
109    /// Register all SPARQL 1.2 built-in functions
110    fn register_sparql_12_functions(&mut self) {
111        // String functions
112        self.register_native("CONCAT", Arc::new(fn_concat));
113        self.register_native("STRLEN", Arc::new(fn_strlen));
114        self.register_native("SUBSTR", Arc::new(fn_substr));
115        self.register_native("REPLACE", Arc::new(fn_replace));
116        self.register_native("REGEX", Arc::new(fn_regex));
117        self.register_native("STRAFTER", Arc::new(fn_strafter));
118        self.register_native("STRBEFORE", Arc::new(fn_strbefore));
119        self.register_native("STRSTARTS", Arc::new(fn_strstarts));
120        self.register_native("STRENDS", Arc::new(fn_strends));
121        self.register_native("CONTAINS", Arc::new(fn_contains));
122        self.register_native("ENCODE_FOR_URI", Arc::new(fn_encode_for_uri));
123
124        // Case functions
125        self.register_native("UCASE", Arc::new(fn_ucase));
126        self.register_native("LCASE", Arc::new(fn_lcase));
127
128        // Numeric functions
129        self.register_native("ABS", Arc::new(fn_abs));
130        self.register_native("CEIL", Arc::new(fn_ceil));
131        self.register_native("FLOOR", Arc::new(fn_floor));
132        self.register_native("ROUND", Arc::new(fn_round));
133        self.register_native("RAND", Arc::new(fn_rand));
134
135        // Math functions (SPARQL 1.2 additions)
136        self.register_native("SQRT", Arc::new(fn_sqrt));
137        self.register_native("SIN", Arc::new(fn_sin));
138        self.register_native("COS", Arc::new(fn_cos));
139        self.register_native("TAN", Arc::new(fn_tan));
140        self.register_native("ASIN", Arc::new(fn_asin));
141        self.register_native("ACOS", Arc::new(fn_acos));
142        self.register_native("ATAN", Arc::new(fn_atan));
143        self.register_native("ATAN2", Arc::new(fn_atan2));
144        self.register_native("EXP", Arc::new(fn_exp));
145        self.register_native("LOG", Arc::new(fn_log));
146        self.register_native("LOG10", Arc::new(fn_log10));
147        self.register_native("POW", Arc::new(fn_pow));
148
149        // Date/time functions
150        self.register_native("NOW", Arc::new(fn_now));
151        self.register_native("YEAR", Arc::new(fn_year));
152        self.register_native("MONTH", Arc::new(fn_month));
153        self.register_native("DAY", Arc::new(fn_day));
154        self.register_native("HOURS", Arc::new(fn_hours));
155        self.register_native("MINUTES", Arc::new(fn_minutes));
156        self.register_native("SECONDS", Arc::new(fn_seconds));
157        self.register_native("TIMEZONE", Arc::new(fn_timezone));
158        self.register_native("TZ", Arc::new(fn_tz));
159
160        // Hash functions (SPARQL 1.2)
161        self.register_native("SHA1", Arc::new(fn_sha1));
162        self.register_native("SHA256", Arc::new(fn_sha256));
163        self.register_native("SHA384", Arc::new(fn_sha384));
164        self.register_native("SHA512", Arc::new(fn_sha512));
165        self.register_native("MD5", Arc::new(fn_md5));
166
167        // Type functions
168        self.register_native("STR", Arc::new(fn_str));
169        self.register_native("LANG", Arc::new(fn_lang));
170        self.register_native("DATATYPE", Arc::new(fn_datatype));
171        self.register_native("IRI", Arc::new(fn_iri));
172        self.register_native("URI", Arc::new(fn_iri)); // Alias
173        self.register_native("BNODE", Arc::new(fn_bnode));
174        self.register_native("STRDT", Arc::new(fn_strdt));
175        self.register_native("STRLANG", Arc::new(fn_strlang));
176        self.register_native("UUID", Arc::new(fn_uuid));
177        self.register_native("STRUUID", Arc::new(fn_struuid));
178
179        // Aggregate functions
180        self.register_native("COUNT", Arc::new(fn_count));
181        self.register_native("SUM", Arc::new(fn_sum));
182        self.register_native("AVG", Arc::new(fn_avg));
183        self.register_native("MIN", Arc::new(fn_min));
184        self.register_native("MAX", Arc::new(fn_max));
185        self.register_native("GROUP_CONCAT", Arc::new(fn_group_concat));
186        self.register_native("SAMPLE", Arc::new(fn_sample));
187
188        // Boolean functions
189        self.register_native("NOT", Arc::new(fn_not));
190        self.register_native("EXISTS", Arc::new(fn_exists));
191        self.register_native("NOT_EXISTS", Arc::new(fn_not_exists));
192        self.register_native("BOUND", Arc::new(fn_bound));
193        self.register_native("COALESCE", Arc::new(fn_coalesce));
194        self.register_native("IF", Arc::new(fn_if));
195
196        // List functions (SPARQL 1.2)
197        self.register_native("IN", Arc::new(fn_in));
198        self.register_native("NOT_IN", Arc::new(fn_not_in));
199    }
200
201    /// Register a native function
202    fn register_native(&mut self, name: &str, func: NativeFunction) {
203        self.functions
204            .insert(name.to_string(), FunctionImpl::Native(func));
205    }
206
207    /// Register a custom function
208    pub fn register_custom(&mut self, func: Arc<dyn CustomFunction>) {
209        let metadata = func.metadata();
210        self.extensions.insert(metadata.name.clone(), func);
211    }
212
213    /// Execute a function
214    pub fn execute(&self, name: &str, args: &[Term]) -> Result<Term, OxirsError> {
215        // Check built-in functions
216        if let Some(func) = self.functions.get(name) {
217            match func {
218                FunctionImpl::Native(f) => f(args),
219                FunctionImpl::JavaScript(_) => Err(OxirsError::Query(
220                    "JavaScript functions not yet implemented".to_string(),
221                )),
222                FunctionImpl::Wasm(_) => Err(OxirsError::Query(
223                    "WASM functions not yet implemented".to_string(),
224                )),
225            }
226        }
227        // Check custom functions
228        else if let Some(func) = self.extensions.get(name) {
229            func.execute(args)
230        } else {
231            Err(OxirsError::Query(format!("Unknown function: {name}")))
232        }
233    }
234}
235
236// String functions implementation
237
238fn fn_concat(args: &[Term]) -> Result<Term, OxirsError> {
239    let mut result = String::new();
240
241    for arg in args {
242        match arg {
243            Term::Literal(lit) => result.push_str(lit.value()),
244            Term::NamedNode(nn) => result.push_str(nn.as_str()),
245            _ => {
246                return Err(OxirsError::Query(
247                    "CONCAT requires string arguments".to_string(),
248                ))
249            }
250        }
251    }
252
253    Ok(Term::Literal(Literal::new(&result)))
254}
255
256fn fn_strlen(args: &[Term]) -> Result<Term, OxirsError> {
257    if args.len() != 1 {
258        return Err(OxirsError::Query(
259            "STRLEN requires exactly 1 argument".to_string(),
260        ));
261    }
262
263    match &args[0] {
264        Term::Literal(lit) => {
265            let len = lit.value().chars().count() as i64;
266            Ok(Term::Literal(Literal::new_typed(
267                len.to_string(),
268                NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
269            )))
270        }
271        _ => Err(OxirsError::Query(
272            "STRLEN requires string literal".to_string(),
273        )),
274    }
275}
276
277fn fn_substr(args: &[Term]) -> Result<Term, OxirsError> {
278    if args.len() < 2 || args.len() > 3 {
279        return Err(OxirsError::Query(
280            "SUBSTR requires 2 or 3 arguments".to_string(),
281        ));
282    }
283
284    match (&args[0], &args[1]) {
285        (Term::Literal(str_lit), Term::Literal(start_lit)) => {
286            let string = str_lit.value();
287            let start = start_lit
288                .value()
289                .parse::<usize>()
290                .map_err(|_| OxirsError::Query("Invalid start position".to_string()))?;
291
292            let result = if args.len() == 3 {
293                match &args[2] {
294                    Term::Literal(len_lit) => {
295                        let len = len_lit
296                            .value()
297                            .parse::<usize>()
298                            .map_err(|_| OxirsError::Query("Invalid length".to_string()))?;
299                        string.chars().skip(start - 1).take(len).collect::<String>()
300                    }
301                    _ => return Err(OxirsError::Query("Length must be numeric".to_string())),
302                }
303            } else {
304                string.chars().skip(start - 1).collect::<String>()
305            };
306
307            Ok(Term::Literal(Literal::new(&result)))
308        }
309        _ => Err(OxirsError::Query(
310            "SUBSTR requires string and numeric arguments".to_string(),
311        )),
312    }
313}
314
315fn fn_replace(args: &[Term]) -> Result<Term, OxirsError> {
316    if args.len() < 3 || args.len() > 4 {
317        return Err(OxirsError::Query(
318            "REPLACE requires 3 or 4 arguments".to_string(),
319        ));
320    }
321
322    match (&args[0], &args[1], &args[2]) {
323        (Term::Literal(text), Term::Literal(pattern), Term::Literal(replacement)) => {
324            let flags = if args.len() == 4 {
325                match &args[3] {
326                    Term::Literal(f) => f.value(),
327                    _ => return Err(OxirsError::Query("Flags must be string".to_string())),
328                }
329            } else {
330                ""
331            };
332
333            // Build regex with flags
334            let regex_str = if flags.contains('i') {
335                format!("(?i){}", pattern.value())
336            } else {
337                pattern.value().to_string()
338            };
339
340            let regex = Regex::new(&regex_str)
341                .map_err(|e| OxirsError::Query(format!("Invalid regex: {e}")))?;
342
343            let result = regex.replace_all(text.value(), replacement.value());
344            Ok(Term::Literal(Literal::new(result.as_ref())))
345        }
346        _ => Err(OxirsError::Query(
347            "REPLACE requires string arguments".to_string(),
348        )),
349    }
350}
351
352fn fn_regex(args: &[Term]) -> Result<Term, OxirsError> {
353    if args.len() < 2 || args.len() > 3 {
354        return Err(OxirsError::Query(
355            "REGEX requires 2 or 3 arguments".to_string(),
356        ));
357    }
358
359    match (&args[0], &args[1]) {
360        (Term::Literal(text), Term::Literal(pattern)) => {
361            let flags = if args.len() == 3 {
362                match &args[2] {
363                    Term::Literal(f) => f.value(),
364                    _ => return Err(OxirsError::Query("Flags must be string".to_string())),
365                }
366            } else {
367                ""
368            };
369
370            let regex_str = if flags.contains('i') {
371                format!("(?i){}", pattern.value())
372            } else {
373                pattern.value().to_string()
374            };
375
376            let regex = Regex::new(&regex_str)
377                .map_err(|e| OxirsError::Query(format!("Invalid regex: {e}")))?;
378
379            let matches = regex.is_match(text.value());
380            Ok(Term::Literal(Literal::new_typed(
381                if matches { "true" } else { "false" },
382                NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean").unwrap(),
383            )))
384        }
385        _ => Err(OxirsError::Query(
386            "REGEX requires string arguments".to_string(),
387        )),
388    }
389}
390
391// String manipulation functions
392
393fn fn_strafter(args: &[Term]) -> Result<Term, OxirsError> {
394    if args.len() != 2 {
395        return Err(OxirsError::Query(
396            "STRAFTER requires exactly 2 arguments".to_string(),
397        ));
398    }
399
400    match (&args[0], &args[1]) {
401        (Term::Literal(str_lit), Term::Literal(after_lit)) => {
402            let string = str_lit.value();
403            let after = after_lit.value();
404
405            if let Some(pos) = string.find(after) {
406                let result = &string[pos + after.len()..];
407                Ok(Term::Literal(Literal::new(result)))
408            } else {
409                Ok(Term::Literal(Literal::new("")))
410            }
411        }
412        _ => Err(OxirsError::Query(
413            "STRAFTER requires string arguments".to_string(),
414        )),
415    }
416}
417
418fn fn_strbefore(args: &[Term]) -> Result<Term, OxirsError> {
419    if args.len() != 2 {
420        return Err(OxirsError::Query(
421            "STRBEFORE requires exactly 2 arguments".to_string(),
422        ));
423    }
424
425    match (&args[0], &args[1]) {
426        (Term::Literal(str_lit), Term::Literal(before_lit)) => {
427            let string = str_lit.value();
428            let before = before_lit.value();
429
430            if let Some(pos) = string.find(before) {
431                let result = &string[..pos];
432                Ok(Term::Literal(Literal::new(result)))
433            } else {
434                Ok(Term::Literal(Literal::new("")))
435            }
436        }
437        _ => Err(OxirsError::Query(
438            "STRBEFORE requires string arguments".to_string(),
439        )),
440    }
441}
442
443fn fn_strstarts(args: &[Term]) -> Result<Term, OxirsError> {
444    if args.len() != 2 {
445        return Err(OxirsError::Query(
446            "STRSTARTS requires exactly 2 arguments".to_string(),
447        ));
448    }
449
450    match (&args[0], &args[1]) {
451        (Term::Literal(str_lit), Term::Literal(prefix_lit)) => {
452            let result = str_lit.value().starts_with(prefix_lit.value());
453            Ok(Term::Literal(Literal::new_typed(
454                if result { "true" } else { "false" },
455                NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean").unwrap(),
456            )))
457        }
458        _ => Err(OxirsError::Query(
459            "STRSTARTS requires string arguments".to_string(),
460        )),
461    }
462}
463
464fn fn_strends(args: &[Term]) -> Result<Term, OxirsError> {
465    if args.len() != 2 {
466        return Err(OxirsError::Query(
467            "STRENDS requires exactly 2 arguments".to_string(),
468        ));
469    }
470
471    match (&args[0], &args[1]) {
472        (Term::Literal(str_lit), Term::Literal(suffix_lit)) => {
473            let result = str_lit.value().ends_with(suffix_lit.value());
474            Ok(Term::Literal(Literal::new_typed(
475                if result { "true" } else { "false" },
476                NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean").unwrap(),
477            )))
478        }
479        _ => Err(OxirsError::Query(
480            "STRENDS requires string arguments".to_string(),
481        )),
482    }
483}
484
485fn fn_contains(args: &[Term]) -> Result<Term, OxirsError> {
486    if args.len() != 2 {
487        return Err(OxirsError::Query(
488            "CONTAINS requires exactly 2 arguments".to_string(),
489        ));
490    }
491
492    match (&args[0], &args[1]) {
493        (Term::Literal(str_lit), Term::Literal(substr_lit)) => {
494            let result = str_lit.value().contains(substr_lit.value());
495            Ok(Term::Literal(Literal::new_typed(
496                if result { "true" } else { "false" },
497                NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean").unwrap(),
498            )))
499        }
500        _ => Err(OxirsError::Query(
501            "CONTAINS requires string arguments".to_string(),
502        )),
503    }
504}
505
506fn fn_encode_for_uri(args: &[Term]) -> Result<Term, OxirsError> {
507    if args.len() != 1 {
508        return Err(OxirsError::Query(
509            "ENCODE_FOR_URI requires exactly 1 argument".to_string(),
510        ));
511    }
512
513    match &args[0] {
514        Term::Literal(lit) => {
515            let encoded = urlencoding::encode(lit.value());
516            Ok(Term::Literal(Literal::new(encoded.as_ref())))
517        }
518        _ => Err(OxirsError::Query(
519            "ENCODE_FOR_URI requires string argument".to_string(),
520        )),
521    }
522}
523
524// Case functions
525
526fn fn_ucase(args: &[Term]) -> Result<Term, OxirsError> {
527    if args.len() != 1 {
528        return Err(OxirsError::Query(
529            "UCASE requires exactly 1 argument".to_string(),
530        ));
531    }
532
533    match &args[0] {
534        Term::Literal(lit) => Ok(Term::Literal(Literal::new(lit.value().to_uppercase()))),
535        _ => Err(OxirsError::Query(
536            "UCASE requires string argument".to_string(),
537        )),
538    }
539}
540
541fn fn_lcase(args: &[Term]) -> Result<Term, OxirsError> {
542    if args.len() != 1 {
543        return Err(OxirsError::Query(
544            "LCASE requires exactly 1 argument".to_string(),
545        ));
546    }
547
548    match &args[0] {
549        Term::Literal(lit) => Ok(Term::Literal(Literal::new(lit.value().to_lowercase()))),
550        _ => Err(OxirsError::Query(
551            "LCASE requires string argument".to_string(),
552        )),
553    }
554}
555
556// Numeric functions
557
558fn fn_abs(args: &[Term]) -> Result<Term, OxirsError> {
559    if args.len() != 1 {
560        return Err(OxirsError::Query(
561            "ABS requires exactly 1 argument".to_string(),
562        ));
563    }
564
565    match &args[0] {
566        Term::Literal(lit) => {
567            let value = lit
568                .value()
569                .parse::<f64>()
570                .map_err(|_| OxirsError::Query("ABS requires numeric argument".to_string()))?;
571            let result = value.abs();
572
573            // Preserve datatype
574            let dt = lit.datatype();
575            if dt.as_str() == "http://www.w3.org/2001/XMLSchema#integer" {
576                Ok(Term::Literal(Literal::new_typed(
577                    (result as i64).to_string(),
578                    NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
579                )))
580            } else {
581                Ok(Term::Literal(Literal::new_typed(
582                    result.to_string(),
583                    NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
584                )))
585            }
586        }
587        _ => Err(OxirsError::Query(
588            "ABS requires numeric literal".to_string(),
589        )),
590    }
591}
592
593fn fn_ceil(args: &[Term]) -> Result<Term, OxirsError> {
594    if args.len() != 1 {
595        return Err(OxirsError::Query(
596            "CEIL requires exactly 1 argument".to_string(),
597        ));
598    }
599
600    match &args[0] {
601        Term::Literal(lit) => {
602            let value = lit
603                .value()
604                .parse::<f64>()
605                .map_err(|_| OxirsError::Query("CEIL requires numeric argument".to_string()))?;
606            let result = value.ceil() as i64;
607            Ok(Term::Literal(Literal::new_typed(
608                result.to_string(),
609                NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
610            )))
611        }
612        _ => Err(OxirsError::Query(
613            "CEIL requires numeric literal".to_string(),
614        )),
615    }
616}
617
618fn fn_floor(args: &[Term]) -> Result<Term, OxirsError> {
619    if args.len() != 1 {
620        return Err(OxirsError::Query(
621            "FLOOR requires exactly 1 argument".to_string(),
622        ));
623    }
624
625    match &args[0] {
626        Term::Literal(lit) => {
627            let value = lit
628                .value()
629                .parse::<f64>()
630                .map_err(|_| OxirsError::Query("FLOOR requires numeric argument".to_string()))?;
631            let result = value.floor() as i64;
632            Ok(Term::Literal(Literal::new_typed(
633                result.to_string(),
634                NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
635            )))
636        }
637        _ => Err(OxirsError::Query(
638            "FLOOR requires numeric literal".to_string(),
639        )),
640    }
641}
642
643fn fn_round(args: &[Term]) -> Result<Term, OxirsError> {
644    if args.len() != 1 {
645        return Err(OxirsError::Query(
646            "ROUND requires exactly 1 argument".to_string(),
647        ));
648    }
649
650    match &args[0] {
651        Term::Literal(lit) => {
652            let value = lit
653                .value()
654                .parse::<f64>()
655                .map_err(|_| OxirsError::Query("ROUND requires numeric argument".to_string()))?;
656            let result = value.round() as i64;
657            Ok(Term::Literal(Literal::new_typed(
658                result.to_string(),
659                NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
660            )))
661        }
662        _ => Err(OxirsError::Query(
663            "ROUND requires numeric literal".to_string(),
664        )),
665    }
666}
667
668fn fn_rand(_args: &[Term]) -> Result<Term, OxirsError> {
669    use scirs2_core::random::{Random, Rng};
670    let mut random = Random::default();
671    let value: f64 = random.random();
672    Ok(Term::Literal(Literal::new_typed(
673        value.to_string(),
674        NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
675    )))
676}
677
678// Math functions (SPARQL 1.2)
679
680fn fn_sqrt(args: &[Term]) -> Result<Term, OxirsError> {
681    if args.len() != 1 {
682        return Err(OxirsError::Query(
683            "SQRT requires exactly 1 argument".to_string(),
684        ));
685    }
686
687    match &args[0] {
688        Term::Literal(lit) => {
689            let value = lit
690                .value()
691                .parse::<f64>()
692                .map_err(|_| OxirsError::Query("SQRT requires numeric argument".to_string()))?;
693            if value < 0.0 {
694                return Err(OxirsError::Query("SQRT of negative number".to_string()));
695            }
696            let result = value.sqrt();
697            Ok(Term::Literal(Literal::new_typed(
698                result.to_string(),
699                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
700            )))
701        }
702        _ => Err(OxirsError::Query(
703            "SQRT requires numeric literal".to_string(),
704        )),
705    }
706}
707
708fn fn_sin(args: &[Term]) -> Result<Term, OxirsError> {
709    if args.len() != 1 {
710        return Err(OxirsError::Query(
711            "SIN requires exactly 1 argument".to_string(),
712        ));
713    }
714
715    match &args[0] {
716        Term::Literal(lit) => {
717            let value = lit
718                .value()
719                .parse::<f64>()
720                .map_err(|_| OxirsError::Query("SIN requires numeric argument".to_string()))?;
721            let result = value.sin();
722            Ok(Term::Literal(Literal::new_typed(
723                result.to_string(),
724                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
725            )))
726        }
727        _ => Err(OxirsError::Query(
728            "SIN requires numeric literal".to_string(),
729        )),
730    }
731}
732
733fn fn_cos(args: &[Term]) -> Result<Term, OxirsError> {
734    if args.len() != 1 {
735        return Err(OxirsError::Query(
736            "COS requires exactly 1 argument".to_string(),
737        ));
738    }
739
740    match &args[0] {
741        Term::Literal(lit) => {
742            let value = lit
743                .value()
744                .parse::<f64>()
745                .map_err(|_| OxirsError::Query("COS requires numeric argument".to_string()))?;
746            let result = value.cos();
747            Ok(Term::Literal(Literal::new_typed(
748                result.to_string(),
749                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
750            )))
751        }
752        _ => Err(OxirsError::Query(
753            "COS requires numeric literal".to_string(),
754        )),
755    }
756}
757
758fn fn_tan(args: &[Term]) -> Result<Term, OxirsError> {
759    if args.len() != 1 {
760        return Err(OxirsError::Query(
761            "TAN requires exactly 1 argument".to_string(),
762        ));
763    }
764
765    match &args[0] {
766        Term::Literal(lit) => {
767            let value = lit
768                .value()
769                .parse::<f64>()
770                .map_err(|_| OxirsError::Query("TAN requires numeric argument".to_string()))?;
771            let result = value.tan();
772            Ok(Term::Literal(Literal::new_typed(
773                result.to_string(),
774                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
775            )))
776        }
777        _ => Err(OxirsError::Query(
778            "TAN requires numeric literal".to_string(),
779        )),
780    }
781}
782
783fn fn_asin(args: &[Term]) -> Result<Term, OxirsError> {
784    if args.len() != 1 {
785        return Err(OxirsError::Query(
786            "ASIN requires exactly 1 argument".to_string(),
787        ));
788    }
789
790    match &args[0] {
791        Term::Literal(lit) => {
792            let value = lit
793                .value()
794                .parse::<f64>()
795                .map_err(|_| OxirsError::Query("ASIN requires numeric argument".to_string()))?;
796            if !(-1.0..=1.0).contains(&value) {
797                return Err(OxirsError::Query(
798                    "ASIN argument must be between -1 and 1".to_string(),
799                ));
800            }
801            let result = value.asin();
802            Ok(Term::Literal(Literal::new_typed(
803                result.to_string(),
804                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
805            )))
806        }
807        _ => Err(OxirsError::Query(
808            "ASIN requires numeric literal".to_string(),
809        )),
810    }
811}
812
813fn fn_acos(args: &[Term]) -> Result<Term, OxirsError> {
814    if args.len() != 1 {
815        return Err(OxirsError::Query(
816            "ACOS requires exactly 1 argument".to_string(),
817        ));
818    }
819
820    match &args[0] {
821        Term::Literal(lit) => {
822            let value = lit
823                .value()
824                .parse::<f64>()
825                .map_err(|_| OxirsError::Query("ACOS requires numeric argument".to_string()))?;
826            if !(-1.0..=1.0).contains(&value) {
827                return Err(OxirsError::Query(
828                    "ACOS argument must be between -1 and 1".to_string(),
829                ));
830            }
831            let result = value.acos();
832            Ok(Term::Literal(Literal::new_typed(
833                result.to_string(),
834                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
835            )))
836        }
837        _ => Err(OxirsError::Query(
838            "ACOS requires numeric literal".to_string(),
839        )),
840    }
841}
842
843fn fn_atan(args: &[Term]) -> Result<Term, OxirsError> {
844    if args.len() != 1 {
845        return Err(OxirsError::Query(
846            "ATAN requires exactly 1 argument".to_string(),
847        ));
848    }
849
850    match &args[0] {
851        Term::Literal(lit) => {
852            let value = lit
853                .value()
854                .parse::<f64>()
855                .map_err(|_| OxirsError::Query("ATAN requires numeric argument".to_string()))?;
856            let result = value.atan();
857            Ok(Term::Literal(Literal::new_typed(
858                result.to_string(),
859                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
860            )))
861        }
862        _ => Err(OxirsError::Query(
863            "ATAN requires numeric literal".to_string(),
864        )),
865    }
866}
867
868fn fn_atan2(args: &[Term]) -> Result<Term, OxirsError> {
869    if args.len() != 2 {
870        return Err(OxirsError::Query(
871            "ATAN2 requires exactly 2 arguments".to_string(),
872        ));
873    }
874
875    match (&args[0], &args[1]) {
876        (Term::Literal(y_lit), Term::Literal(x_lit)) => {
877            let y = y_lit
878                .value()
879                .parse::<f64>()
880                .map_err(|_| OxirsError::Query("ATAN2 requires numeric arguments".to_string()))?;
881            let x = x_lit
882                .value()
883                .parse::<f64>()
884                .map_err(|_| OxirsError::Query("ATAN2 requires numeric arguments".to_string()))?;
885            let result = y.atan2(x);
886            Ok(Term::Literal(Literal::new_typed(
887                result.to_string(),
888                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
889            )))
890        }
891        _ => Err(OxirsError::Query(
892            "ATAN2 requires numeric literals".to_string(),
893        )),
894    }
895}
896
897fn fn_exp(args: &[Term]) -> Result<Term, OxirsError> {
898    if args.len() != 1 {
899        return Err(OxirsError::Query(
900            "EXP requires exactly 1 argument".to_string(),
901        ));
902    }
903
904    match &args[0] {
905        Term::Literal(lit) => {
906            let value = lit
907                .value()
908                .parse::<f64>()
909                .map_err(|_| OxirsError::Query("EXP requires numeric argument".to_string()))?;
910            let result = value.exp();
911            Ok(Term::Literal(Literal::new_typed(
912                result.to_string(),
913                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
914            )))
915        }
916        _ => Err(OxirsError::Query(
917            "EXP requires numeric literal".to_string(),
918        )),
919    }
920}
921
922fn fn_log(args: &[Term]) -> Result<Term, OxirsError> {
923    if args.len() != 1 {
924        return Err(OxirsError::Query(
925            "LOG requires exactly 1 argument".to_string(),
926        ));
927    }
928
929    match &args[0] {
930        Term::Literal(lit) => {
931            let value = lit
932                .value()
933                .parse::<f64>()
934                .map_err(|_| OxirsError::Query("LOG requires numeric argument".to_string()))?;
935            if value <= 0.0 {
936                return Err(OxirsError::Query("LOG of non-positive number".to_string()));
937            }
938            let result = value.ln();
939            Ok(Term::Literal(Literal::new_typed(
940                result.to_string(),
941                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
942            )))
943        }
944        _ => Err(OxirsError::Query(
945            "LOG requires numeric literal".to_string(),
946        )),
947    }
948}
949
950fn fn_log10(args: &[Term]) -> Result<Term, OxirsError> {
951    if args.len() != 1 {
952        return Err(OxirsError::Query(
953            "LOG10 requires exactly 1 argument".to_string(),
954        ));
955    }
956
957    match &args[0] {
958        Term::Literal(lit) => {
959            let value = lit
960                .value()
961                .parse::<f64>()
962                .map_err(|_| OxirsError::Query("LOG10 requires numeric argument".to_string()))?;
963            if value <= 0.0 {
964                return Err(OxirsError::Query(
965                    "LOG10 of non-positive number".to_string(),
966                ));
967            }
968            let result = value.log10();
969            Ok(Term::Literal(Literal::new_typed(
970                result.to_string(),
971                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
972            )))
973        }
974        _ => Err(OxirsError::Query(
975            "LOG10 requires numeric literal".to_string(),
976        )),
977    }
978}
979
980fn fn_pow(args: &[Term]) -> Result<Term, OxirsError> {
981    if args.len() != 2 {
982        return Err(OxirsError::Query(
983            "POW requires exactly 2 arguments".to_string(),
984        ));
985    }
986
987    match (&args[0], &args[1]) {
988        (Term::Literal(base_lit), Term::Literal(exp_lit)) => {
989            let base = base_lit
990                .value()
991                .parse::<f64>()
992                .map_err(|_| OxirsError::Query("POW requires numeric arguments".to_string()))?;
993            let exp = exp_lit
994                .value()
995                .parse::<f64>()
996                .map_err(|_| OxirsError::Query("POW requires numeric arguments".to_string()))?;
997            let result = base.powf(exp);
998            Ok(Term::Literal(Literal::new_typed(
999                result.to_string(),
1000                NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
1001            )))
1002        }
1003        _ => Err(OxirsError::Query(
1004            "POW requires numeric literals".to_string(),
1005        )),
1006    }
1007}
1008
1009// Date/time functions
1010
1011fn fn_now(_args: &[Term]) -> Result<Term, OxirsError> {
1012    let now = Utc::now();
1013    Ok(Term::Literal(Literal::new_typed(
1014        now.to_rfc3339(),
1015        NamedNode::new("http://www.w3.org/2001/XMLSchema#dateTime").unwrap(),
1016    )))
1017}
1018
1019fn fn_year(args: &[Term]) -> Result<Term, OxirsError> {
1020    if args.len() != 1 {
1021        return Err(OxirsError::Query(
1022            "YEAR requires exactly 1 argument".to_string(),
1023        ));
1024    }
1025
1026    match &args[0] {
1027        Term::Literal(lit) => {
1028            let dt = DateTime::parse_from_rfc3339(lit.value())
1029                .map_err(|_| OxirsError::Query("Invalid dateTime".to_string()))?;
1030            Ok(Term::Literal(Literal::new_typed(
1031                dt.year().to_string(),
1032                NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
1033            )))
1034        }
1035        _ => Err(OxirsError::Query(
1036            "YEAR requires dateTime literal".to_string(),
1037        )),
1038    }
1039}
1040
1041fn fn_month(args: &[Term]) -> Result<Term, OxirsError> {
1042    if args.len() != 1 {
1043        return Err(OxirsError::Query(
1044            "MONTH requires exactly 1 argument".to_string(),
1045        ));
1046    }
1047
1048    match &args[0] {
1049        Term::Literal(lit) => {
1050            let dt = DateTime::parse_from_rfc3339(lit.value())
1051                .map_err(|_| OxirsError::Query("Invalid dateTime".to_string()))?;
1052            Ok(Term::Literal(Literal::new_typed(
1053                dt.month().to_string(),
1054                NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
1055            )))
1056        }
1057        _ => Err(OxirsError::Query(
1058            "MONTH requires dateTime literal".to_string(),
1059        )),
1060    }
1061}
1062
1063fn fn_day(args: &[Term]) -> Result<Term, OxirsError> {
1064    if args.len() != 1 {
1065        return Err(OxirsError::Query(
1066            "DAY requires exactly 1 argument".to_string(),
1067        ));
1068    }
1069
1070    match &args[0] {
1071        Term::Literal(lit) => {
1072            let dt = DateTime::parse_from_rfc3339(lit.value())
1073                .map_err(|_| OxirsError::Query("Invalid dateTime".to_string()))?;
1074            Ok(Term::Literal(Literal::new_typed(
1075                dt.day().to_string(),
1076                NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
1077            )))
1078        }
1079        _ => Err(OxirsError::Query(
1080            "DAY requires dateTime literal".to_string(),
1081        )),
1082    }
1083}
1084
1085fn fn_hours(args: &[Term]) -> Result<Term, OxirsError> {
1086    if args.len() != 1 {
1087        return Err(OxirsError::Query(
1088            "HOURS requires exactly 1 argument".to_string(),
1089        ));
1090    }
1091
1092    match &args[0] {
1093        Term::Literal(lit) => {
1094            let dt = DateTime::parse_from_rfc3339(lit.value())
1095                .map_err(|_| OxirsError::Query("Invalid dateTime".to_string()))?;
1096            Ok(Term::Literal(Literal::new_typed(
1097                dt.hour().to_string(),
1098                NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
1099            )))
1100        }
1101        _ => Err(OxirsError::Query(
1102            "HOURS requires dateTime literal".to_string(),
1103        )),
1104    }
1105}
1106
1107fn fn_minutes(args: &[Term]) -> Result<Term, OxirsError> {
1108    if args.len() != 1 {
1109        return Err(OxirsError::Query(
1110            "MINUTES requires exactly 1 argument".to_string(),
1111        ));
1112    }
1113
1114    match &args[0] {
1115        Term::Literal(lit) => {
1116            let dt = DateTime::parse_from_rfc3339(lit.value())
1117                .map_err(|_| OxirsError::Query("Invalid dateTime".to_string()))?;
1118            Ok(Term::Literal(Literal::new_typed(
1119                dt.minute().to_string(),
1120                NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
1121            )))
1122        }
1123        _ => Err(OxirsError::Query(
1124            "MINUTES requires dateTime literal".to_string(),
1125        )),
1126    }
1127}
1128
1129fn fn_seconds(args: &[Term]) -> Result<Term, OxirsError> {
1130    if args.len() != 1 {
1131        return Err(OxirsError::Query(
1132            "SECONDS requires exactly 1 argument".to_string(),
1133        ));
1134    }
1135
1136    match &args[0] {
1137        Term::Literal(lit) => {
1138            let dt = DateTime::parse_from_rfc3339(lit.value())
1139                .map_err(|_| OxirsError::Query("Invalid dateTime".to_string()))?;
1140            Ok(Term::Literal(Literal::new_typed(
1141                format!("{}.{:09}", dt.second(), dt.nanosecond()),
1142                NamedNode::new("http://www.w3.org/2001/XMLSchema#decimal").unwrap(),
1143            )))
1144        }
1145        _ => Err(OxirsError::Query(
1146            "SECONDS requires dateTime literal".to_string(),
1147        )),
1148    }
1149}
1150
1151fn fn_timezone(args: &[Term]) -> Result<Term, OxirsError> {
1152    if args.len() != 1 {
1153        return Err(OxirsError::Query(
1154            "TIMEZONE requires exactly 1 argument".to_string(),
1155        ));
1156    }
1157
1158    match &args[0] {
1159        Term::Literal(lit) => {
1160            let dt = DateTime::parse_from_rfc3339(lit.value())
1161                .map_err(|_| OxirsError::Query("Invalid dateTime".to_string()))?;
1162            let offset = dt.offset();
1163            let hours = offset.local_minus_utc() / 3600;
1164            let minutes = (offset.local_minus_utc() % 3600) / 60;
1165
1166            let duration = if hours == 0 && minutes == 0 {
1167                "PT0S".to_string()
1168            } else {
1169                format!("PT{}H{}M", hours.abs(), minutes.abs())
1170            };
1171
1172            Ok(Term::Literal(Literal::new_typed(
1173                &duration,
1174                NamedNode::new("http://www.w3.org/2001/XMLSchema#dayTimeDuration").unwrap(),
1175            )))
1176        }
1177        _ => Err(OxirsError::Query(
1178            "TIMEZONE requires dateTime literal".to_string(),
1179        )),
1180    }
1181}
1182
1183fn fn_tz(args: &[Term]) -> Result<Term, OxirsError> {
1184    if args.len() != 1 {
1185        return Err(OxirsError::Query(
1186            "TZ requires exactly 1 argument".to_string(),
1187        ));
1188    }
1189
1190    match &args[0] {
1191        Term::Literal(lit) => {
1192            let dt = DateTime::parse_from_rfc3339(lit.value())
1193                .map_err(|_| OxirsError::Query("Invalid dateTime".to_string()))?;
1194            let offset = dt.offset();
1195            let hours = offset.local_minus_utc() / 3600;
1196            let minutes = (offset.local_minus_utc() % 3600) / 60;
1197
1198            let tz_string = if hours == 0 && minutes == 0 {
1199                "Z".to_string()
1200            } else {
1201                format!("{:+03}:{:02}", hours, minutes.abs())
1202            };
1203
1204            Ok(Term::Literal(Literal::new(&tz_string)))
1205        }
1206        _ => Err(OxirsError::Query(
1207            "TZ requires dateTime literal".to_string(),
1208        )),
1209    }
1210}
1211
1212// Hash functions
1213
1214fn fn_sha1(args: &[Term]) -> Result<Term, OxirsError> {
1215    if args.len() != 1 {
1216        return Err(OxirsError::Query(
1217            "SHA1 requires exactly 1 argument".to_string(),
1218        ));
1219    }
1220
1221    match &args[0] {
1222        Term::Literal(lit) => {
1223            use sha1::{Digest, Sha1};
1224            let mut hasher = Sha1::new();
1225            hasher.update(lit.value().as_bytes());
1226            let result = hasher.finalize();
1227            let hex = format!("{result:x}");
1228            Ok(Term::Literal(Literal::new(&hex)))
1229        }
1230        _ => Err(OxirsError::Query(
1231            "SHA1 requires string literal".to_string(),
1232        )),
1233    }
1234}
1235
1236fn fn_sha256(args: &[Term]) -> Result<Term, OxirsError> {
1237    if args.len() != 1 {
1238        return Err(OxirsError::Query(
1239            "SHA256 requires exactly 1 argument".to_string(),
1240        ));
1241    }
1242
1243    match &args[0] {
1244        Term::Literal(lit) => {
1245            use sha2::{Digest, Sha256};
1246            let mut hasher = Sha256::new();
1247            hasher.update(lit.value().as_bytes());
1248            let result = hasher.finalize();
1249            let hex = format!("{result:x}");
1250            Ok(Term::Literal(Literal::new(&hex)))
1251        }
1252        _ => Err(OxirsError::Query(
1253            "SHA256 requires string literal".to_string(),
1254        )),
1255    }
1256}
1257
1258fn fn_sha384(args: &[Term]) -> Result<Term, OxirsError> {
1259    if args.len() != 1 {
1260        return Err(OxirsError::Query(
1261            "SHA384 requires exactly 1 argument".to_string(),
1262        ));
1263    }
1264
1265    match &args[0] {
1266        Term::Literal(lit) => {
1267            use sha2::{Digest, Sha384};
1268            let mut hasher = Sha384::new();
1269            hasher.update(lit.value().as_bytes());
1270            let result = hasher.finalize();
1271            let hex = format!("{result:x}");
1272            Ok(Term::Literal(Literal::new(&hex)))
1273        }
1274        _ => Err(OxirsError::Query(
1275            "SHA384 requires string literal".to_string(),
1276        )),
1277    }
1278}
1279
1280fn fn_sha512(args: &[Term]) -> Result<Term, OxirsError> {
1281    if args.len() != 1 {
1282        return Err(OxirsError::Query(
1283            "SHA512 requires exactly 1 argument".to_string(),
1284        ));
1285    }
1286
1287    match &args[0] {
1288        Term::Literal(lit) => {
1289            use sha2::{Digest, Sha512};
1290            let mut hasher = Sha512::new();
1291            hasher.update(lit.value().as_bytes());
1292            let result = hasher.finalize();
1293            let hex = format!("{result:x}");
1294            Ok(Term::Literal(Literal::new(&hex)))
1295        }
1296        _ => Err(OxirsError::Query(
1297            "SHA512 requires string literal".to_string(),
1298        )),
1299    }
1300}
1301
1302fn fn_md5(args: &[Term]) -> Result<Term, OxirsError> {
1303    if args.len() != 1 {
1304        return Err(OxirsError::Query(
1305            "MD5 requires exactly 1 argument".to_string(),
1306        ));
1307    }
1308
1309    match &args[0] {
1310        Term::Literal(lit) => {
1311            let mut hasher = md5::Context::new();
1312            hasher.consume(lit.value().as_bytes());
1313            let result = hasher.finalize();
1314            let hex = format!("{result:x}");
1315            Ok(Term::Literal(Literal::new(&hex)))
1316        }
1317        _ => Err(OxirsError::Query("MD5 requires string literal".to_string())),
1318    }
1319}
1320
1321// Type conversion functions
1322
1323fn fn_str(args: &[Term]) -> Result<Term, OxirsError> {
1324    if args.len() != 1 {
1325        return Err(OxirsError::Query(
1326            "STR requires exactly 1 argument".to_string(),
1327        ));
1328    }
1329
1330    match &args[0] {
1331        Term::Literal(lit) => Ok(Term::Literal(Literal::new(lit.value()))),
1332        Term::NamedNode(nn) => Ok(Term::Literal(Literal::new(nn.as_str()))),
1333        _ => Err(OxirsError::Query("STR requires literal or IRI".to_string())),
1334    }
1335}
1336
1337fn fn_lang(args: &[Term]) -> Result<Term, OxirsError> {
1338    if args.len() != 1 {
1339        return Err(OxirsError::Query(
1340            "LANG requires exactly 1 argument".to_string(),
1341        ));
1342    }
1343
1344    match &args[0] {
1345        Term::Literal(lit) => {
1346            let lang = lit.language().unwrap_or("");
1347            Ok(Term::Literal(Literal::new(lang)))
1348        }
1349        _ => Err(OxirsError::Query("LANG requires literal".to_string())),
1350    }
1351}
1352
1353fn fn_datatype(args: &[Term]) -> Result<Term, OxirsError> {
1354    if args.len() != 1 {
1355        return Err(OxirsError::Query(
1356            "DATATYPE requires exactly 1 argument".to_string(),
1357        ));
1358    }
1359
1360    match &args[0] {
1361        Term::Literal(lit) => {
1362            let dt = lit.datatype();
1363            Ok(Term::NamedNode(NamedNode::new(dt.as_str()).unwrap()))
1364        }
1365        _ => Err(OxirsError::Query("DATATYPE requires literal".to_string())),
1366    }
1367}
1368
1369fn fn_iri(args: &[Term]) -> Result<Term, OxirsError> {
1370    if args.len() != 1 {
1371        return Err(OxirsError::Query(
1372            "IRI requires exactly 1 argument".to_string(),
1373        ));
1374    }
1375
1376    match &args[0] {
1377        Term::Literal(lit) => {
1378            let iri = NamedNode::new(lit.value())?;
1379            Ok(Term::NamedNode(iri))
1380        }
1381        Term::NamedNode(nn) => Ok(Term::NamedNode(nn.clone())),
1382        _ => Err(OxirsError::Query(
1383            "IRI requires string literal or IRI".to_string(),
1384        )),
1385    }
1386}
1387
1388fn fn_bnode(args: &[Term]) -> Result<Term, OxirsError> {
1389    use crate::model::BlankNode;
1390
1391    if args.is_empty() {
1392        Ok(Term::BlankNode(BlankNode::new_unique()))
1393    } else if args.len() == 1 {
1394        match &args[0] {
1395            Term::Literal(lit) => {
1396                let bnode = BlankNode::new(lit.value())?;
1397                Ok(Term::BlankNode(bnode))
1398            }
1399            _ => Err(OxirsError::Query(
1400                "BNODE requires string literal or no arguments".to_string(),
1401            )),
1402        }
1403    } else {
1404        Err(OxirsError::Query(
1405            "BNODE requires 0 or 1 arguments".to_string(),
1406        ))
1407    }
1408}
1409
1410fn fn_strdt(args: &[Term]) -> Result<Term, OxirsError> {
1411    if args.len() != 2 {
1412        return Err(OxirsError::Query(
1413            "STRDT requires exactly 2 arguments".to_string(),
1414        ));
1415    }
1416
1417    match (&args[0], &args[1]) {
1418        (Term::Literal(value_lit), Term::NamedNode(datatype)) => Ok(Term::Literal(
1419            Literal::new_typed(value_lit.value(), datatype.clone()),
1420        )),
1421        _ => Err(OxirsError::Query(
1422            "STRDT requires string literal and IRI".to_string(),
1423        )),
1424    }
1425}
1426
1427fn fn_strlang(args: &[Term]) -> Result<Term, OxirsError> {
1428    if args.len() != 2 {
1429        return Err(OxirsError::Query(
1430            "STRLANG requires exactly 2 arguments".to_string(),
1431        ));
1432    }
1433
1434    match (&args[0], &args[1]) {
1435        (Term::Literal(value_lit), Term::Literal(lang_lit)) => Ok(Term::Literal(
1436            Literal::new_lang(value_lit.value(), lang_lit.value())?,
1437        )),
1438        _ => Err(OxirsError::Query(
1439            "STRLANG requires two string literals".to_string(),
1440        )),
1441    }
1442}
1443
1444fn fn_uuid(_args: &[Term]) -> Result<Term, OxirsError> {
1445    use uuid::Uuid;
1446    let uuid = Uuid::new_v4();
1447    let iri = NamedNode::new(format!("urn:uuid:{uuid}"))?;
1448    Ok(Term::NamedNode(iri))
1449}
1450
1451fn fn_struuid(_args: &[Term]) -> Result<Term, OxirsError> {
1452    use uuid::Uuid;
1453    let uuid = Uuid::new_v4();
1454    Ok(Term::Literal(Literal::new(uuid.to_string())))
1455}
1456
1457// Aggregate functions (placeholder implementations)
1458
1459fn fn_count(_args: &[Term]) -> Result<Term, OxirsError> {
1460    // Aggregate functions need special handling in query evaluation
1461    Err(OxirsError::Query(
1462        "COUNT is an aggregate function".to_string(),
1463    ))
1464}
1465
1466fn fn_sum(_args: &[Term]) -> Result<Term, OxirsError> {
1467    Err(OxirsError::Query(
1468        "SUM is an aggregate function".to_string(),
1469    ))
1470}
1471
1472fn fn_avg(_args: &[Term]) -> Result<Term, OxirsError> {
1473    Err(OxirsError::Query(
1474        "AVG is an aggregate function".to_string(),
1475    ))
1476}
1477
1478fn fn_min(_args: &[Term]) -> Result<Term, OxirsError> {
1479    Err(OxirsError::Query(
1480        "MIN is an aggregate function".to_string(),
1481    ))
1482}
1483
1484fn fn_max(_args: &[Term]) -> Result<Term, OxirsError> {
1485    Err(OxirsError::Query(
1486        "MAX is an aggregate function".to_string(),
1487    ))
1488}
1489
1490fn fn_group_concat(_args: &[Term]) -> Result<Term, OxirsError> {
1491    Err(OxirsError::Query(
1492        "GROUP_CONCAT is an aggregate function".to_string(),
1493    ))
1494}
1495
1496fn fn_sample(_args: &[Term]) -> Result<Term, OxirsError> {
1497    Err(OxirsError::Query(
1498        "SAMPLE is an aggregate function".to_string(),
1499    ))
1500}
1501
1502// Boolean functions
1503
1504fn fn_not(args: &[Term]) -> Result<Term, OxirsError> {
1505    if args.len() != 1 {
1506        return Err(OxirsError::Query(
1507            "NOT requires exactly 1 argument".to_string(),
1508        ));
1509    }
1510
1511    match &args[0] {
1512        Term::Literal(lit) => {
1513            let value = lit.value() == "true";
1514            Ok(Term::Literal(Literal::new_typed(
1515                if !value { "true" } else { "false" },
1516                NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean").unwrap(),
1517            )))
1518        }
1519        _ => Err(OxirsError::Query(
1520            "NOT requires boolean literal".to_string(),
1521        )),
1522    }
1523}
1524
1525fn fn_exists(_args: &[Term]) -> Result<Term, OxirsError> {
1526    // EXISTS needs special handling in query evaluation
1527    Err(OxirsError::Query(
1528        "EXISTS requires graph pattern context".to_string(),
1529    ))
1530}
1531
1532fn fn_not_exists(_args: &[Term]) -> Result<Term, OxirsError> {
1533    // NOT EXISTS needs special handling in query evaluation
1534    Err(OxirsError::Query(
1535        "NOT EXISTS requires graph pattern context".to_string(),
1536    ))
1537}
1538
1539fn fn_bound(args: &[Term]) -> Result<Term, OxirsError> {
1540    if args.len() != 1 {
1541        return Err(OxirsError::Query(
1542            "BOUND requires exactly 1 argument".to_string(),
1543        ));
1544    }
1545
1546    let is_bound = !matches!(&args[0], Term::Variable(_));
1547    Ok(Term::Literal(Literal::new_typed(
1548        if is_bound { "true" } else { "false" },
1549        NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean").unwrap(),
1550    )))
1551}
1552
1553fn fn_coalesce(args: &[Term]) -> Result<Term, OxirsError> {
1554    for arg in args {
1555        if !matches!(arg, Term::Variable(_)) {
1556            return Ok(arg.clone());
1557        }
1558    }
1559    Err(OxirsError::Query(
1560        "COALESCE: all arguments are unbound".to_string(),
1561    ))
1562}
1563
1564fn fn_if(args: &[Term]) -> Result<Term, OxirsError> {
1565    if args.len() != 3 {
1566        return Err(OxirsError::Query(
1567            "IF requires exactly 3 arguments".to_string(),
1568        ));
1569    }
1570
1571    match &args[0] {
1572        Term::Literal(condition) => {
1573            let is_true = condition.value() == "true";
1574            Ok(if is_true {
1575                args[1].clone()
1576            } else {
1577                args[2].clone()
1578            })
1579        }
1580        _ => Err(OxirsError::Query(
1581            "IF condition must be boolean".to_string(),
1582        )),
1583    }
1584}
1585
1586// List functions
1587
1588fn fn_in(args: &[Term]) -> Result<Term, OxirsError> {
1589    if args.len() < 2 {
1590        return Err(OxirsError::Query(
1591            "IN requires at least 2 arguments".to_string(),
1592        ));
1593    }
1594
1595    let value = &args[0];
1596    for item in &args[1..] {
1597        if value == item {
1598            return Ok(Term::Literal(Literal::new_typed(
1599                "true",
1600                NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean").unwrap(),
1601            )));
1602        }
1603    }
1604
1605    Ok(Term::Literal(Literal::new_typed(
1606        "false",
1607        NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean").unwrap(),
1608    )))
1609}
1610
1611fn fn_not_in(args: &[Term]) -> Result<Term, OxirsError> {
1612    match fn_in(args)? {
1613        Term::Literal(lit) => {
1614            let value = lit.value() == "true";
1615            Ok(Term::Literal(Literal::new_typed(
1616                if !value { "true" } else { "false" },
1617                NamedNode::new("http://www.w3.org/2001/XMLSchema#boolean").unwrap(),
1618            )))
1619        }
1620        _ => unreachable!(),
1621    }
1622}
1623
1624#[cfg(test)]
1625mod tests {
1626    use super::*;
1627
1628    #[test]
1629    fn test_string_functions() {
1630        let registry = FunctionRegistry::new();
1631
1632        // Test CONCAT
1633        let args = vec![
1634            Term::Literal(Literal::new("Hello")),
1635            Term::Literal(Literal::new(" ")),
1636            Term::Literal(Literal::new("World")),
1637        ];
1638        let result = registry.execute("CONCAT", &args).unwrap();
1639        match result {
1640            Term::Literal(lit) => assert_eq!(lit.value(), "Hello World"),
1641            _ => panic!("Expected literal"),
1642        }
1643
1644        // Test STRLEN
1645        let args = vec![Term::Literal(Literal::new("Hello"))];
1646        let result = registry.execute("STRLEN", &args).unwrap();
1647        match result {
1648            Term::Literal(lit) => assert_eq!(lit.value(), "5"),
1649            _ => panic!("Expected literal"),
1650        }
1651
1652        // Test UCASE
1653        let args = vec![Term::Literal(Literal::new("hello"))];
1654        let result = registry.execute("UCASE", &args).unwrap();
1655        match result {
1656            Term::Literal(lit) => assert_eq!(lit.value(), "HELLO"),
1657            _ => panic!("Expected literal"),
1658        }
1659    }
1660
1661    #[test]
1662    fn test_numeric_functions() {
1663        let registry = FunctionRegistry::new();
1664
1665        // Test ABS
1666        let args = vec![Term::Literal(Literal::new_typed(
1667            "-42",
1668            NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
1669        ))];
1670        let result = registry.execute("ABS", &args).unwrap();
1671        match result {
1672            Term::Literal(lit) => assert_eq!(lit.value(), "42"),
1673            _ => panic!("Expected literal"),
1674        }
1675
1676        // Test SQRT
1677        let args = vec![Term::Literal(Literal::new_typed(
1678            "9",
1679            NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
1680        ))];
1681        let result = registry.execute("SQRT", &args).unwrap();
1682        match result {
1683            Term::Literal(lit) => {
1684                let value: f64 = lit.value().parse().unwrap();
1685                assert!((value - 3.0).abs() < 0.0001);
1686            }
1687            _ => panic!("Expected literal"),
1688        }
1689    }
1690
1691    #[test]
1692    fn test_hash_functions() {
1693        let registry = FunctionRegistry::new();
1694
1695        // Test SHA256
1696        let args = vec![Term::Literal(Literal::new("test"))];
1697        let result = registry.execute("SHA256", &args).unwrap();
1698        match result {
1699            Term::Literal(lit) => {
1700                assert_eq!(
1701                    lit.value(),
1702                    "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
1703                );
1704            }
1705            _ => panic!("Expected literal"),
1706        }
1707    }
1708}