Skip to main content

oxirs_core/sparql/
expressions.rs

1//! SPARQL expression evaluation: BIND clause and functions
2
3use crate::error::OxirsError;
4use crate::model::{Literal, Term};
5use crate::rdf_store::VariableBinding;
6use crate::sparql::aggregates::find_matching_paren;
7use crate::Result;
8
9/// Expression types for BIND clause
10#[derive(Debug, Clone)]
11pub enum Expression {
12    Variable(String),
13    Literal(String),
14    Integer(i64),
15    Float(f64),
16    ArithmeticOp {
17        left: Box<Expression>,
18        op: ArithmeticOperator,
19        right: Box<Expression>,
20    },
21    FunctionCall {
22        name: String,
23        args: Vec<Expression>,
24    },
25}
26
27/// Arithmetic operators for expressions
28#[derive(Debug, Clone)]
29pub enum ArithmeticOperator {
30    Add,
31    Subtract,
32    Multiply,
33    Divide,
34}
35
36/// BIND clause for variable assignment
37#[derive(Debug, Clone)]
38pub struct BindExpression {
39    pub expression: Expression,
40    pub variable: String,
41}
42
43/// Extract BIND clauses from WHERE clause
44pub fn extract_bind_expressions(sparql: &str) -> Result<Vec<BindExpression>> {
45    let mut binds = Vec::new();
46
47    // Find all BIND clauses
48    let sparql_upper = sparql.to_uppercase();
49    let mut search_pos = 0;
50
51    while let Some(bind_pos) = sparql_upper[search_pos..].find("BIND") {
52        let abs_pos = search_pos + bind_pos;
53        let after_bind = &sparql[abs_pos + 4..];
54
55        // Find the opening parenthesis
56        if let Some(paren_start) = after_bind.find('(') {
57            // Find matching closing parenthesis
58            if let Some(paren_end) = find_matching_paren(&after_bind[paren_start..]) {
59                let bind_content = &after_bind[paren_start + 1..paren_start + paren_end];
60
61                // Parse BIND content: expression AS ?variable
62                if let Some(as_pos) = bind_content.to_uppercase().find(" AS ") {
63                    let expr_text = bind_content[..as_pos].trim();
64                    let var_text = bind_content[as_pos + 4..].trim();
65
66                    // Extract variable name
67                    if let Some(var_name) = var_text.strip_prefix('?') {
68                        // Parse expression
69                        if let Ok(expression) = parse_expression(expr_text) {
70                            binds.push(BindExpression {
71                                expression,
72                                variable: var_name.to_string(),
73                            });
74                        }
75                    }
76                }
77
78                search_pos = abs_pos + 4 + paren_start + paren_end;
79            } else {
80                break;
81            }
82        } else {
83            break;
84        }
85    }
86
87    Ok(binds)
88}
89
90/// Split function arguments respecting quotes and parentheses
91pub fn split_function_args(args_text: &str) -> Vec<String> {
92    let mut args = Vec::new();
93    let mut current_arg = String::new();
94    let mut in_string = false;
95    let mut string_delimiter = ' ';
96    let mut paren_depth = 0;
97
98    for ch in args_text.chars() {
99        match ch {
100            '"' | '\'' if !in_string => {
101                in_string = true;
102                string_delimiter = ch;
103                current_arg.push(ch);
104            }
105            '"' | '\'' if in_string && ch == string_delimiter => {
106                in_string = false;
107                current_arg.push(ch);
108            }
109            '(' if !in_string => {
110                paren_depth += 1;
111                current_arg.push(ch);
112            }
113            ')' if !in_string => {
114                paren_depth -= 1;
115                current_arg.push(ch);
116            }
117            ',' if !in_string && paren_depth == 0 => {
118                if !current_arg.trim().is_empty() {
119                    args.push(current_arg.clone());
120                }
121                current_arg.clear();
122            }
123            _ => {
124                current_arg.push(ch);
125            }
126        }
127    }
128
129    // Don't forget the last argument
130    if !current_arg.trim().is_empty() {
131        args.push(current_arg);
132    }
133
134    args
135}
136
137/// Parse an expression for BIND
138pub fn parse_expression(expr: &str) -> Result<Expression> {
139    let expr = expr.trim();
140
141    // Check for arithmetic operators (simple parsing - left to right)
142    if let Some(op_pos) = expr.rfind(" + ") {
143        let left = parse_expression(&expr[..op_pos])?;
144        let right = parse_expression(&expr[op_pos + 3..])?;
145        return Ok(Expression::ArithmeticOp {
146            left: Box::new(left),
147            op: ArithmeticOperator::Add,
148            right: Box::new(right),
149        });
150    }
151
152    if let Some(op_pos) = expr.rfind(" - ") {
153        let left = parse_expression(&expr[..op_pos])?;
154        let right = parse_expression(&expr[op_pos + 3..])?;
155        return Ok(Expression::ArithmeticOp {
156            left: Box::new(left),
157            op: ArithmeticOperator::Subtract,
158            right: Box::new(right),
159        });
160    }
161
162    if let Some(op_pos) = expr.rfind(" * ") {
163        let left = parse_expression(&expr[..op_pos])?;
164        let right = parse_expression(&expr[op_pos + 3..])?;
165        return Ok(Expression::ArithmeticOp {
166            left: Box::new(left),
167            op: ArithmeticOperator::Multiply,
168            right: Box::new(right),
169        });
170    }
171
172    if let Some(op_pos) = expr.rfind(" / ") {
173        let left = parse_expression(&expr[..op_pos])?;
174        let right = parse_expression(&expr[op_pos + 3..])?;
175        return Ok(Expression::ArithmeticOp {
176            left: Box::new(left),
177            op: ArithmeticOperator::Divide,
178            right: Box::new(right),
179        });
180    }
181
182    // Check for function calls
183    if let Some(paren_pos) = expr.find('(') {
184        if let Some(paren_end) = find_matching_paren(&expr[paren_pos..]) {
185            let func_name = expr[..paren_pos].trim().to_uppercase();
186            let args_text = &expr[paren_pos + 1..paren_pos + paren_end];
187
188            // Parse function arguments
189            let arg_strs = split_function_args(args_text);
190            let mut args = Vec::new();
191            for arg_str in arg_strs {
192                args.push(parse_expression(arg_str.trim())?);
193            }
194
195            return Ok(Expression::FunctionCall {
196                name: func_name,
197                args,
198            });
199        }
200    }
201
202    // Check for variables
203    if expr.starts_with('?') {
204        return Ok(Expression::Variable(expr.to_string()));
205    }
206
207    // Check for string literals
208    if (expr.starts_with('"') && expr.ends_with('"'))
209        || (expr.starts_with('\'') && expr.ends_with('\''))
210    {
211        return Ok(Expression::Literal(expr[1..expr.len() - 1].to_string()));
212    }
213
214    // Check for numeric literals
215    if let Ok(int_val) = expr.parse::<i64>() {
216        return Ok(Expression::Integer(int_val));
217    }
218
219    if let Ok(float_val) = expr.parse::<f64>() {
220        return Ok(Expression::Float(float_val));
221    }
222
223    // Default to literal
224    Ok(Expression::Literal(expr.to_string()))
225}
226
227/// Evaluate an expression against a binding
228pub fn evaluate_expression(expr: &Expression, binding: &VariableBinding) -> Result<Term> {
229    match expr {
230        Expression::Variable(var_name) => {
231            let var = var_name.strip_prefix('?').unwrap_or(var_name);
232            binding
233                .get(var)
234                .cloned()
235                .ok_or_else(|| OxirsError::Query(format!("Unbound variable: {}", var_name)))
236        }
237        Expression::Literal(val) => Ok(Term::from(Literal::new(val.clone()))),
238        Expression::Integer(val) => Ok(Term::from(Literal::new(val.to_string()))),
239        Expression::Float(val) => Ok(Term::from(Literal::new(val.to_string()))),
240        Expression::ArithmeticOp { left, op, right } => {
241            let left_val = evaluate_expression(left, binding)?;
242            let right_val = evaluate_expression(right, binding)?;
243
244            let left_num = term_to_number(&left_val)?;
245            let right_num = term_to_number(&right_val)?;
246
247            let result = match op {
248                ArithmeticOperator::Add => left_num + right_num,
249                ArithmeticOperator::Subtract => left_num - right_num,
250                ArithmeticOperator::Multiply => left_num * right_num,
251                ArithmeticOperator::Divide => {
252                    if right_num.abs() < f64::EPSILON {
253                        return Err(OxirsError::Query("Division by zero".to_string()));
254                    }
255                    left_num / right_num
256                }
257            };
258
259            Ok(Term::from(Literal::new(result.to_string())))
260        }
261        Expression::FunctionCall { name, args } => {
262            match name.as_str() {
263                "STR" => {
264                    if args.len() != 1 {
265                        return Err(OxirsError::Query(
266                            "STR requires exactly one argument".to_string(),
267                        ));
268                    }
269                    let val = evaluate_expression(&args[0], binding)?;
270                    Ok(Term::from(Literal::new(term_to_string(&val))))
271                }
272                "CONCAT" => {
273                    let mut result = String::new();
274                    for arg in args {
275                        let val = evaluate_expression(arg, binding)?;
276                        result.push_str(&term_to_string(&val));
277                    }
278                    Ok(Term::from(Literal::new(result)))
279                }
280                "STRLEN" => {
281                    if args.len() != 1 {
282                        return Err(OxirsError::Query(
283                            "STRLEN requires exactly one argument".to_string(),
284                        ));
285                    }
286                    let val = evaluate_expression(&args[0], binding)?;
287                    let s = term_to_string(&val);
288                    Ok(Term::from(Literal::new(s.len().to_string())))
289                }
290                "UCASE" => {
291                    if args.len() != 1 {
292                        return Err(OxirsError::Query(
293                            "UCASE requires exactly one argument".to_string(),
294                        ));
295                    }
296                    let val = evaluate_expression(&args[0], binding)?;
297                    let s = term_to_string(&val);
298                    Ok(Term::from(Literal::new(s.to_uppercase())))
299                }
300                "LCASE" => {
301                    if args.len() != 1 {
302                        return Err(OxirsError::Query(
303                            "LCASE requires exactly one argument".to_string(),
304                        ));
305                    }
306                    let val = evaluate_expression(&args[0], binding)?;
307                    let s = term_to_string(&val);
308                    Ok(Term::from(Literal::new(s.to_lowercase())))
309                }
310                "CONTAINS" => {
311                    if args.len() != 2 {
312                        return Err(OxirsError::Query(
313                            "CONTAINS requires exactly two arguments".to_string(),
314                        ));
315                    }
316                    let haystack = evaluate_expression(&args[0], binding)?;
317                    let needle = evaluate_expression(&args[1], binding)?;
318                    let haystack_str = term_to_string(&haystack);
319                    let needle_str = term_to_string(&needle);
320                    let result = if haystack_str.contains(&needle_str) {
321                        "true"
322                    } else {
323                        "false"
324                    };
325                    Ok(Term::from(Literal::new(result.to_string())))
326                }
327                "SUBSTR" | "SUBSTRING" => {
328                    if args.len() < 2 || args.len() > 3 {
329                        return Err(OxirsError::Query(
330                            "SUBSTR requires 2 or 3 arguments".to_string(),
331                        ));
332                    }
333                    let s = term_to_string(&evaluate_expression(&args[0], binding)?);
334                    let start = term_to_number(&evaluate_expression(&args[1], binding)?)? as usize;
335
336                    // SPARQL uses 1-based indexing
337                    let start_idx = if start > 0 { start - 1 } else { 0 };
338
339                    let result: String = if args.len() == 3 {
340                        let length =
341                            term_to_number(&evaluate_expression(&args[2], binding)?)? as usize;
342                        s.chars().skip(start_idx).take(length).collect()
343                    } else {
344                        s.chars().skip(start_idx).collect()
345                    };
346
347                    Ok(Term::from(Literal::new(result)))
348                }
349                "REPLACE" => {
350                    if args.len() < 3 || args.len() > 4 {
351                        return Err(OxirsError::Query(
352                            "REPLACE requires 3 or 4 arguments".to_string(),
353                        ));
354                    }
355                    let s = term_to_string(&evaluate_expression(&args[0], binding)?);
356                    let pattern = term_to_string(&evaluate_expression(&args[1], binding)?);
357                    let replacement = term_to_string(&evaluate_expression(&args[2], binding)?);
358
359                    // Simple string replacement (not regex for now)
360                    let result = s.replace(&pattern, &replacement);
361                    Ok(Term::from(Literal::new(result)))
362                }
363                "STRSTARTS" => {
364                    if args.len() != 2 {
365                        return Err(OxirsError::Query(
366                            "STRSTARTS requires exactly two arguments".to_string(),
367                        ));
368                    }
369                    let s = term_to_string(&evaluate_expression(&args[0], binding)?);
370                    let prefix = term_to_string(&evaluate_expression(&args[1], binding)?);
371                    let result = if s.starts_with(&prefix) {
372                        "true"
373                    } else {
374                        "false"
375                    };
376                    Ok(Term::from(Literal::new(result.to_string())))
377                }
378                "STRENDS" => {
379                    if args.len() != 2 {
380                        return Err(OxirsError::Query(
381                            "STRENDS requires exactly two arguments".to_string(),
382                        ));
383                    }
384                    let s = term_to_string(&evaluate_expression(&args[0], binding)?);
385                    let suffix = term_to_string(&evaluate_expression(&args[1], binding)?);
386                    let result = if s.ends_with(&suffix) {
387                        "true"
388                    } else {
389                        "false"
390                    };
391                    Ok(Term::from(Literal::new(result.to_string())))
392                }
393                // ========================================
394                // SPARQL 1.2 RDF-star Built-in Functions
395                // ========================================
396                //
397                // These functions provide full support for RDF-star quoted triples,
398                // enabling meta-statements about statements.
399                //
400                // Specification: https://w3c.github.io/rdf-star/cg-spec/
401                // Performance: All operations are O(1)
402                //
403                // Functions:
404                // - TRIPLE(s, p, o) → Creates a quoted triple
405                // - SUBJECT(qt) → Extracts subject from quoted triple
406                // - PREDICATE(qt) → Extracts predicate from quoted triple
407                // - OBJECT(qt) → Extracts object from quoted triple
408                // - isTRIPLE(term) → Tests if term is a quoted triple
409                // ========================================
410
411                // TRIPLE(subject, predicate, object) → quoted triple
412                //
413                // Creates a quoted triple from three terms. Supports nested
414                // quoted triples for meta-meta-statements.
415                //
416                // Example SPARQL:
417                //   BIND(TRIPLE(?s, ?p, ?o) AS ?qt)
418                //   BIND(TRIPLE(TRIPLE(?s1, ?p1, ?o1), ?p2, ?o2) AS ?nested)
419                "TRIPLE" => {
420                    if args.len() != 3 {
421                        return Err(OxirsError::Query(
422                            "TRIPLE requires exactly three arguments (subject, predicate, object)"
423                                .to_string(),
424                        ));
425                    }
426                    let subject_term = evaluate_expression(&args[0], binding)?;
427                    let predicate_term = evaluate_expression(&args[1], binding)?;
428                    let object_term = evaluate_expression(&args[2], binding)?;
429
430                    // Convert Term to Subject/Predicate/Object
431                    use crate::model::{Object, Predicate, QuotedTriple, Subject, Triple};
432                    let subject: Subject = match subject_term {
433                        Term::NamedNode(n) => Subject::NamedNode(n),
434                        Term::BlankNode(b) => Subject::BlankNode(b),
435                        Term::Variable(v) => Subject::Variable(v),
436                        Term::QuotedTriple(qt) => Subject::QuotedTriple(qt),
437                        _ => {
438                            return Err(OxirsError::Query(
439                                "TRIPLE subject must be NamedNode, BlankNode, Variable, or QuotedTriple".to_string(),
440                            ))
441                        }
442                    };
443
444                    let predicate: Predicate = match predicate_term {
445                        Term::NamedNode(n) => Predicate::NamedNode(n),
446                        Term::Variable(v) => Predicate::Variable(v),
447                        _ => {
448                            return Err(OxirsError::Query(
449                                "TRIPLE predicate must be NamedNode or Variable".to_string(),
450                            ))
451                        }
452                    };
453
454                    let object: Object = match object_term {
455                        Term::NamedNode(n) => Object::NamedNode(n),
456                        Term::BlankNode(b) => Object::BlankNode(b),
457                        Term::Literal(l) => Object::Literal(l),
458                        Term::Variable(v) => Object::Variable(v),
459                        Term::QuotedTriple(qt) => Object::QuotedTriple(qt),
460                    };
461
462                    // Create a Triple and wrap in QuotedTriple
463                    let triple = Triple::new(subject, predicate, object);
464                    let quoted = QuotedTriple::new(triple);
465                    Ok(Term::QuotedTriple(Box::new(quoted)))
466                }
467                // SUBJECT(quotedTriple) → term
468                //
469                // Extracts the subject from a quoted triple.
470                //
471                // Example SPARQL:
472                //   SELECT * WHERE {
473                //     ?qt a ex:Statement .
474                //     BIND(SUBJECT(?qt) AS ?subj)
475                //   }
476                "SUBJECT" => {
477                    if args.len() != 1 {
478                        return Err(OxirsError::Query(
479                            "SUBJECT requires exactly one argument (a quoted triple)".to_string(),
480                        ));
481                    }
482                    let term = evaluate_expression(&args[0], binding)?;
483                    match term {
484                        Term::QuotedTriple(triple) => {
485                            // Convert Subject to Term
486                            use crate::model::Subject;
487                            let subject = triple.subject();
488                            let term = match subject {
489                                Subject::NamedNode(n) => Term::NamedNode(n.clone()),
490                                Subject::BlankNode(b) => Term::BlankNode(b.clone()),
491                                Subject::Variable(v) => Term::Variable(v.clone()),
492                                Subject::QuotedTriple(qt) => Term::QuotedTriple(qt.clone()),
493                            };
494                            Ok(term)
495                        }
496                        _ => Err(OxirsError::Query(
497                            "SUBJECT function requires a quoted triple argument".to_string(),
498                        )),
499                    }
500                }
501                // PREDICATE(quotedTriple) → term
502                //
503                // Extracts the predicate from a quoted triple.
504                //
505                // Example SPARQL:
506                //   SELECT ?qt WHERE {
507                //     ?qt ?p ?o .
508                //     FILTER(PREDICATE(?qt) = ex:hasAge)
509                //   }
510                "PREDICATE" => {
511                    if args.len() != 1 {
512                        return Err(OxirsError::Query(
513                            "PREDICATE requires exactly one argument (a quoted triple)".to_string(),
514                        ));
515                    }
516                    let term = evaluate_expression(&args[0], binding)?;
517                    match term {
518                        Term::QuotedTriple(triple) => {
519                            // Convert Predicate to Term
520                            use crate::model::Predicate;
521                            let predicate = triple.predicate();
522                            let term = match predicate {
523                                Predicate::NamedNode(n) => Term::NamedNode(n.clone()),
524                                Predicate::Variable(v) => Term::Variable(v.clone()),
525                            };
526                            Ok(term)
527                        }
528                        _ => Err(OxirsError::Query(
529                            "PREDICATE function requires a quoted triple argument".to_string(),
530                        )),
531                    }
532                }
533                // OBJECT(quotedTriple) → term
534                //
535                // Extracts the object from a quoted triple.
536                //
537                // Example SPARQL:
538                //   SELECT ?qt WHERE {
539                //     ?qt ?p ?confidence .
540                //     FILTER(OBJECT(?qt) = "high"^^xsd:string)
541                //   }
542                "OBJECT" => {
543                    if args.len() != 1 {
544                        return Err(OxirsError::Query(
545                            "OBJECT requires exactly one argument (a quoted triple)".to_string(),
546                        ));
547                    }
548                    let term = evaluate_expression(&args[0], binding)?;
549                    match term {
550                        Term::QuotedTriple(triple) => {
551                            // Convert Object to Term
552                            use crate::model::Object;
553                            let object = triple.object();
554                            let term = match object {
555                                Object::NamedNode(n) => Term::NamedNode(n.clone()),
556                                Object::BlankNode(b) => Term::BlankNode(b.clone()),
557                                Object::Literal(l) => Term::Literal(l.clone()),
558                                Object::Variable(v) => Term::Variable(v.clone()),
559                                Object::QuotedTriple(qt) => Term::QuotedTriple(qt.clone()),
560                            };
561                            Ok(term)
562                        }
563                        _ => Err(OxirsError::Query(
564                            "OBJECT function requires a quoted triple argument".to_string(),
565                        )),
566                    }
567                }
568                // isTRIPLE(term) → boolean
569                //
570                // Tests whether a term is a quoted triple.
571                //
572                // Example SPARQL:
573                //   SELECT * WHERE {
574                //     ?x ?p ?o .
575                //     FILTER(isTRIPLE(?x))
576                //   }
577                "ISTRIPLE" => {
578                    if args.len() != 1 {
579                        return Err(OxirsError::Query(
580                            "isTRIPLE requires exactly one argument".to_string(),
581                        ));
582                    }
583                    let term = evaluate_expression(&args[0], binding)?;
584                    let result = if matches!(term, Term::QuotedTriple(_)) {
585                        "true"
586                    } else {
587                        "false"
588                    };
589                    Ok(Term::from(Literal::new(result.to_string())))
590                }
591                _ => Err(OxirsError::Query(format!("Unsupported function: {}", name))),
592            }
593        }
594    }
595}
596
597/// Convert a term to a number for arithmetic
598pub fn term_to_number(term: &Term) -> Result<f64> {
599    if let Term::Literal(lit) = term {
600        lit.value()
601            .parse::<f64>()
602            .map_err(|_| OxirsError::Query(format!("Cannot convert to number: {}", lit.value())))
603    } else {
604        Err(OxirsError::Query("Expected numeric literal".to_string()))
605    }
606}
607
608/// Convert a term to a string
609pub fn term_to_string(term: &Term) -> String {
610    match term {
611        Term::NamedNode(node) => node.to_string(),
612        Term::BlankNode(node) => node.to_string(),
613        Term::Literal(lit) => lit.value().to_string(),
614        Term::Variable(var) => var.to_string(),
615        Term::QuotedTriple(triple) => format!("<< {} >>", triple),
616    }
617}
618
619/// Apply BIND expressions to results
620pub fn apply_bind_expressions(
621    results: Vec<VariableBinding>,
622    binds: &[BindExpression],
623) -> Result<Vec<VariableBinding>> {
624    if binds.is_empty() {
625        return Ok(results);
626    }
627
628    let mut new_results = Vec::new();
629
630    for binding in results {
631        let mut new_binding = binding.clone();
632
633        // Apply each BIND expression
634        for bind_expr in binds {
635            match evaluate_expression(&bind_expr.expression, &binding) {
636                Ok(value) => {
637                    new_binding.bind(bind_expr.variable.clone(), value);
638                }
639                Err(_) => {
640                    // If evaluation fails, skip this binding
641                    continue;
642                }
643            }
644        }
645
646        new_results.push(new_binding);
647    }
648
649    Ok(new_results)
650}
651
652#[cfg(test)]
653mod tests {
654    use super::*;
655    use crate::model::{Literal, NamedNode, Object, Predicate, Subject, Triple};
656
657    #[test]
658    fn test_sparql_12_triple_function() {
659        // Test TRIPLE() function - creates a quoted triple
660        let mut binding = VariableBinding::new();
661
662        // Set up test data
663        let alice = Term::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"));
664        let knows = Term::NamedNode(NamedNode::new("http://example.org/knows").expect("valid IRI"));
665        let bob = Term::NamedNode(NamedNode::new("http://example.org/bob").expect("valid IRI"));
666
667        binding.bind("s".to_string(), alice.clone());
668        binding.bind("p".to_string(), knows.clone());
669        binding.bind("o".to_string(), bob.clone());
670
671        // Create TRIPLE(?s, ?p, ?o) expression
672        let expr = Expression::FunctionCall {
673            name: "TRIPLE".to_string(),
674            args: vec![
675                Expression::Variable("?s".to_string()),
676                Expression::Variable("?p".to_string()),
677                Expression::Variable("?o".to_string()),
678            ],
679        };
680
681        let result = evaluate_expression(&expr, &binding);
682        assert!(result.is_ok());
683
684        if let Ok(Term::QuotedTriple(qt)) = result {
685            assert_eq!(
686                qt.subject(),
687                &Subject::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"))
688            );
689            assert_eq!(
690                qt.predicate(),
691                &Predicate::NamedNode(
692                    NamedNode::new("http://example.org/knows").expect("valid IRI")
693                )
694            );
695        } else {
696            panic!("Expected QuotedTriple");
697        }
698    }
699
700    #[test]
701    fn test_sparql_12_subject_function() {
702        // Test SUBJECT() function - extracts subject from quoted triple
703        let mut binding = VariableBinding::new();
704
705        // Create a quoted triple
706        let subject =
707            Subject::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"));
708        let predicate =
709            Predicate::NamedNode(NamedNode::new("http://example.org/age").expect("valid IRI"));
710        let object = Object::Literal(Literal::new("30"));
711
712        let triple = Triple::new(subject, predicate, object);
713        let quoted = crate::model::QuotedTriple::new(triple);
714
715        binding.bind("qt".to_string(), Term::QuotedTriple(Box::new(quoted)));
716
717        // Create SUBJECT(?qt) expression
718        let expr = Expression::FunctionCall {
719            name: "SUBJECT".to_string(),
720            args: vec![Expression::Variable("?qt".to_string())],
721        };
722
723        let result = evaluate_expression(&expr, &binding);
724        assert!(result.is_ok());
725
726        if let Ok(Term::NamedNode(n)) = result {
727            assert_eq!(n.as_str(), "http://example.org/alice");
728        } else {
729            panic!("Expected NamedNode");
730        }
731    }
732
733    #[test]
734    fn test_sparql_12_predicate_function() {
735        // Test PREDICATE() function - extracts predicate from quoted triple
736        let mut binding = VariableBinding::new();
737
738        // Create a quoted triple
739        let subject =
740            Subject::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"));
741        let predicate =
742            Predicate::NamedNode(NamedNode::new("http://example.org/age").expect("valid IRI"));
743        let object = Object::Literal(Literal::new("30"));
744
745        let triple = Triple::new(subject, predicate, object);
746        let quoted = crate::model::QuotedTriple::new(triple);
747
748        binding.bind("qt".to_string(), Term::QuotedTriple(Box::new(quoted)));
749
750        // Create PREDICATE(?qt) expression
751        let expr = Expression::FunctionCall {
752            name: "PREDICATE".to_string(),
753            args: vec![Expression::Variable("?qt".to_string())],
754        };
755
756        let result = evaluate_expression(&expr, &binding);
757        assert!(result.is_ok());
758
759        if let Ok(Term::NamedNode(n)) = result {
760            assert_eq!(n.as_str(), "http://example.org/age");
761        } else {
762            panic!("Expected NamedNode");
763        }
764    }
765
766    #[test]
767    fn test_sparql_12_object_function() {
768        // Test OBJECT() function - extracts object from quoted triple
769        let mut binding = VariableBinding::new();
770
771        // Create a quoted triple
772        let subject =
773            Subject::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"));
774        let predicate =
775            Predicate::NamedNode(NamedNode::new("http://example.org/age").expect("valid IRI"));
776        let object = Object::Literal(Literal::new("30"));
777
778        let triple = Triple::new(subject, predicate, object);
779        let quoted = crate::model::QuotedTriple::new(triple);
780
781        binding.bind("qt".to_string(), Term::QuotedTriple(Box::new(quoted)));
782
783        // Create OBJECT(?qt) expression
784        let expr = Expression::FunctionCall {
785            name: "OBJECT".to_string(),
786            args: vec![Expression::Variable("?qt".to_string())],
787        };
788
789        let result = evaluate_expression(&expr, &binding);
790        assert!(result.is_ok());
791
792        if let Ok(Term::Literal(lit)) = result {
793            assert_eq!(lit.value(), "30");
794        } else {
795            panic!("Expected Literal");
796        }
797    }
798
799    #[test]
800    fn test_sparql_12_istriple_function_true() {
801        // Test isTRIPLE() function - returns true for quoted triple
802        let mut binding = VariableBinding::new();
803
804        // Create a quoted triple
805        let subject =
806            Subject::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"));
807        let predicate =
808            Predicate::NamedNode(NamedNode::new("http://example.org/age").expect("valid IRI"));
809        let object = Object::Literal(Literal::new("30"));
810
811        let triple = Triple::new(subject, predicate, object);
812        let quoted = crate::model::QuotedTriple::new(triple);
813
814        binding.bind("qt".to_string(), Term::QuotedTriple(Box::new(quoted)));
815
816        // Create isTRIPLE(?qt) expression
817        let expr = Expression::FunctionCall {
818            name: "ISTRIPLE".to_string(),
819            args: vec![Expression::Variable("?qt".to_string())],
820        };
821
822        let result = evaluate_expression(&expr, &binding);
823        assert!(result.is_ok());
824
825        if let Ok(Term::Literal(lit)) = result {
826            assert_eq!(lit.value(), "true");
827        } else {
828            panic!("Expected true Literal");
829        }
830    }
831
832    #[test]
833    fn test_sparql_12_istriple_function_false() {
834        // Test isTRIPLE() function - returns false for non-quoted triple
835        let mut binding = VariableBinding::new();
836
837        let alice = Term::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"));
838        binding.bind("n".to_string(), alice);
839
840        // Create isTRIPLE(?n) expression
841        let expr = Expression::FunctionCall {
842            name: "ISTRIPLE".to_string(),
843            args: vec![Expression::Variable("?n".to_string())],
844        };
845
846        let result = evaluate_expression(&expr, &binding);
847        assert!(result.is_ok());
848
849        if let Ok(Term::Literal(lit)) = result {
850            assert_eq!(lit.value(), "false");
851        } else {
852            panic!("Expected false Literal");
853        }
854    }
855
856    #[test]
857    fn test_sparql_12_nested_quoted_triples() {
858        // Test nested quoted triples: TRIPLE(TRIPLE(?s, ?p, ?o), ?p2, ?o2)
859        let mut binding = VariableBinding::new();
860
861        let alice = Term::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"));
862        let age = Term::NamedNode(NamedNode::new("http://example.org/age").expect("valid IRI"));
863        let thirty = Term::Literal(Literal::new("30"));
864        let confidence =
865            Term::NamedNode(NamedNode::new("http://example.org/confidence").expect("valid IRI"));
866        let high = Term::Literal(Literal::new("high"));
867
868        binding.bind("s".to_string(), alice);
869        binding.bind("p".to_string(), age);
870        binding.bind("o".to_string(), thirty);
871        binding.bind("p2".to_string(), confidence);
872        binding.bind("o2".to_string(), high);
873
874        // Create inner TRIPLE(?s, ?p, ?o)
875        let inner_expr = Expression::FunctionCall {
876            name: "TRIPLE".to_string(),
877            args: vec![
878                Expression::Variable("?s".to_string()),
879                Expression::Variable("?p".to_string()),
880                Expression::Variable("?o".to_string()),
881            ],
882        };
883
884        // Create outer TRIPLE(inner_triple, ?p2, ?o2)
885        let outer_expr = Expression::FunctionCall {
886            name: "TRIPLE".to_string(),
887            args: vec![
888                inner_expr,
889                Expression::Variable("?p2".to_string()),
890                Expression::Variable("?o2".to_string()),
891            ],
892        };
893
894        let result = evaluate_expression(&outer_expr, &binding);
895        assert!(result.is_ok());
896
897        // The result should be a quoted triple with a quoted triple as subject
898        if let Ok(Term::QuotedTriple(outer_qt)) = result {
899            assert!(matches!(outer_qt.subject(), Subject::QuotedTriple(_)));
900        } else {
901            panic!("Expected nested QuotedTriple");
902        }
903    }
904
905    #[test]
906    fn test_sparql_12_rdf_star_composition() {
907        // Test: Create a triple, then extract its parts
908        let mut binding = VariableBinding::new();
909
910        let alice = Term::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"));
911        let knows = Term::NamedNode(NamedNode::new("http://example.org/knows").expect("valid IRI"));
912        let bob = Term::NamedNode(NamedNode::new("http://example.org/bob").expect("valid IRI"));
913
914        binding.bind("s".to_string(), alice.clone());
915        binding.bind("p".to_string(), knows.clone());
916        binding.bind("o".to_string(), bob.clone());
917
918        // Create TRIPLE(?s, ?p, ?o)
919        let triple_expr = Expression::FunctionCall {
920            name: "TRIPLE".to_string(),
921            args: vec![
922                Expression::Variable("?s".to_string()),
923                Expression::Variable("?p".to_string()),
924                Expression::Variable("?o".to_string()),
925            ],
926        };
927
928        let quoted_triple = evaluate_expression(&triple_expr, &binding)
929            .expect("expression evaluation should succeed");
930        binding.bind("qt".to_string(), quoted_triple);
931
932        // Extract subject
933        let subject_expr = Expression::FunctionCall {
934            name: "SUBJECT".to_string(),
935            args: vec![Expression::Variable("?qt".to_string())],
936        };
937        let extracted_subject = evaluate_expression(&subject_expr, &binding)
938            .expect("expression evaluation should succeed");
939
940        // Verify subject matches original
941        assert_eq!(extracted_subject, alice);
942
943        // Extract predicate
944        let predicate_expr = Expression::FunctionCall {
945            name: "PREDICATE".to_string(),
946            args: vec![Expression::Variable("?qt".to_string())],
947        };
948        let extracted_predicate = evaluate_expression(&predicate_expr, &binding)
949            .expect("expression evaluation should succeed");
950
951        // Verify predicate matches original
952        assert_eq!(extracted_predicate, knows);
953
954        // Extract object
955        let object_expr = Expression::FunctionCall {
956            name: "OBJECT".to_string(),
957            args: vec![Expression::Variable("?qt".to_string())],
958        };
959        let extracted_object = evaluate_expression(&object_expr, &binding)
960            .expect("expression evaluation should succeed");
961
962        // Verify object matches original
963        assert_eq!(extracted_object, bob);
964    }
965}