Skip to main content

oxirs_arq/
triple_functions.rs

1//! RDF-star TRIPLE Functions
2//!
3//! This module implements RDF-star (RDF 1.2) triple term functions
4//! for constructing and deconstructing quoted triples.
5//!
6//! Based on the RDF-star specification and Apache Jena ARQ implementation.
7
8use crate::algebra::{Term, TriplePattern};
9use crate::extensions::{CustomFunction, ExecutionContext, Value, ValueType};
10use anyhow::{bail, Result};
11
12/// Convert Value to Term for triple construction
13fn value_to_term(value: &Value) -> Result<Term> {
14    match value {
15        Value::Iri(iri) => {
16            use oxirs_core::model::NamedNode;
17            let node = NamedNode::new(iri)?;
18            Ok(Term::Iri(node))
19        }
20        Value::Literal {
21            value,
22            language,
23            datatype,
24        } => {
25            use oxirs_core::model::NamedNode;
26            let dt = if let Some(dt_str) = datatype {
27                Some(NamedNode::new(dt_str)?)
28            } else {
29                None
30            };
31            Ok(Term::Literal(crate::algebra::Literal {
32                value: value.clone(),
33                language: language.clone(),
34                datatype: dt,
35            }))
36        }
37        Value::BlankNode(id) => Ok(Term::BlankNode(id.clone())),
38        _ => bail!("Cannot convert {} to RDF term", value.type_name()),
39    }
40}
41
42/// Convert Term to Value for function results
43#[allow(dead_code)]
44fn term_to_value(term: &Term) -> Value {
45    match term {
46        Term::Iri(iri) => Value::Iri(iri.as_str().to_string()),
47        Term::Literal(lit) => Value::Literal {
48            value: lit.value.clone(),
49            language: lit.language.clone(),
50            datatype: lit.datatype.as_ref().map(|dt| dt.as_str().to_string()),
51        },
52        Term::BlankNode(id) => Value::BlankNode(id.clone()),
53        Term::Variable(_v) => {
54            // Variables should not appear in concrete triples
55            // Return as a string representation
56            Value::String(format!("{}", term))
57        }
58        Term::QuotedTriple(triple) => {
59            // Return as a special QuotedTriple value
60            // For now, represent as a string
61            Value::String(format!(
62                "<<{} {} {}>>",
63                triple.subject, triple.predicate, triple.object
64            ))
65        }
66        Term::PropertyPath(path) => Value::String(format!("{}", path)),
67    }
68}
69
70// ============================================================================
71// TRIPLE Function - Construct a quoted triple
72// ============================================================================
73
74/// TRIPLE(?s, ?p, ?o) - Construct a quoted triple from three terms
75///
76/// Creates an RDF-star quoted triple (embedded triple) from subject,
77/// predicate, and object terms.
78///
79/// # Examples
80/// ```sparql
81/// TRIPLE(?s, ?p, ?o) → <<s p o>>
82/// ```
83#[derive(Debug, Clone)]
84pub struct TripleFunction;
85
86impl CustomFunction for TripleFunction {
87    fn name(&self) -> &str {
88        "http://www.w3.org/2001/sw/DataAccess/rq23#triple"
89    }
90
91    fn arity(&self) -> Option<usize> {
92        Some(3)
93    }
94
95    fn parameter_types(&self) -> Vec<ValueType> {
96        vec![
97            ValueType::Union(vec![
98                ValueType::Iri,
99                ValueType::BlankNode,
100                ValueType::Custom("QuotedTriple".to_string()),
101            ]),
102            ValueType::Iri,
103            ValueType::Union(vec![
104                ValueType::Iri,
105                ValueType::BlankNode,
106                ValueType::Literal,
107                ValueType::Custom("QuotedTriple".to_string()),
108            ]),
109        ]
110    }
111
112    fn return_type(&self) -> ValueType {
113        ValueType::Custom("QuotedTriple".to_string())
114    }
115
116    fn documentation(&self) -> &str {
117        "Constructs a quoted triple (RDF-star) from subject, predicate, and object"
118    }
119
120    fn clone_function(&self) -> Box<dyn CustomFunction> {
121        Box::new(self.clone())
122    }
123
124    fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
125        if args.len() != 3 {
126            bail!("TRIPLE() requires exactly 3 arguments (subject, predicate, object)");
127        }
128
129        let subject = value_to_term(&args[0])?;
130        let predicate = value_to_term(&args[1])?;
131        let object = value_to_term(&args[2])?;
132
133        // Validate subject, predicate, object constraints
134        match &subject {
135            Term::Literal(_) => bail!("TRIPLE subject cannot be a literal"),
136            Term::PropertyPath(_) => bail!("TRIPLE subject cannot be a property path"),
137            _ => {}
138        }
139
140        match &predicate {
141            Term::Literal(_) => bail!("TRIPLE predicate cannot be a literal"),
142            Term::BlankNode(_) => bail!("TRIPLE predicate cannot be a blank node"),
143            Term::QuotedTriple(_) => bail!("TRIPLE predicate cannot be a quoted triple"),
144            Term::PropertyPath(_) => bail!("TRIPLE predicate cannot be a property path"),
145            _ => {}
146        }
147
148        // Create quoted triple
149        let triple = TriplePattern {
150            subject,
151            predicate,
152            object,
153        };
154
155        // Return as formatted string (in a full implementation, this would be a proper QuotedTriple value)
156        Ok(Value::String(format!(
157            "<<{} {} {}>>",
158            triple.subject, triple.predicate, triple.object
159        )))
160    }
161}
162
163// ============================================================================
164// SUBJECT Function - Extract subject from quoted triple
165// ============================================================================
166
167/// SUBJECT(?triple) - Extract subject from a quoted triple
168///
169/// Returns the subject component of an RDF-star quoted triple.
170///
171/// # Examples
172/// ```sparql
173/// SUBJECT(<<:s :p :o>>) → :s
174/// ```
175#[derive(Debug, Clone)]
176pub struct SubjectFunction;
177
178impl CustomFunction for SubjectFunction {
179    fn name(&self) -> &str {
180        "http://www.w3.org/2001/sw/DataAccess/rq23#subject"
181    }
182
183    fn arity(&self) -> Option<usize> {
184        Some(1)
185    }
186
187    fn parameter_types(&self) -> Vec<ValueType> {
188        vec![ValueType::Custom("QuotedTriple".to_string())]
189    }
190
191    fn return_type(&self) -> ValueType {
192        ValueType::Union(vec![
193            ValueType::Iri,
194            ValueType::BlankNode,
195            ValueType::Custom("QuotedTriple".to_string()),
196        ])
197    }
198
199    fn documentation(&self) -> &str {
200        "Extracts the subject from a quoted triple (RDF-star)"
201    }
202
203    fn clone_function(&self) -> Box<dyn CustomFunction> {
204        Box::new(self.clone())
205    }
206
207    fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
208        if args.len() != 1 {
209            bail!("SUBJECT() requires exactly 1 argument");
210        }
211
212        // In a full implementation, we would parse the quoted triple from the value
213        // For now, we'll check for a string representation
214        match &args[0] {
215            Value::String(s) if s.starts_with("<<") && s.ends_with(">>") => {
216                // Simple parsing for demonstration
217                // Real implementation would use proper triple term parsing
218                let inner = &s[2..s.len() - 2];
219                let parts: Vec<&str> = inner.split_whitespace().collect();
220                if parts.len() >= 3 {
221                    Ok(Value::Iri(parts[0].to_string()))
222                } else {
223                    bail!("Invalid quoted triple format");
224                }
225            }
226            _ => bail!("SUBJECT() requires a quoted triple argument"),
227        }
228    }
229}
230
231// ============================================================================
232// PREDICATE Function - Extract predicate from quoted triple
233// ============================================================================
234
235/// PREDICATE(?triple) - Extract predicate from a quoted triple
236///
237/// Returns the predicate component of an RDF-star quoted triple.
238///
239/// # Examples
240/// ```sparql
241/// PREDICATE(<<:s :p :o>>) → :p
242/// ```
243#[derive(Debug, Clone)]
244pub struct PredicateFunction;
245
246impl CustomFunction for PredicateFunction {
247    fn name(&self) -> &str {
248        "http://www.w3.org/2001/sw/DataAccess/rq23#predicate"
249    }
250
251    fn arity(&self) -> Option<usize> {
252        Some(1)
253    }
254
255    fn parameter_types(&self) -> Vec<ValueType> {
256        vec![ValueType::Custom("QuotedTriple".to_string())]
257    }
258
259    fn return_type(&self) -> ValueType {
260        ValueType::Iri
261    }
262
263    fn documentation(&self) -> &str {
264        "Extracts the predicate from a quoted triple (RDF-star)"
265    }
266
267    fn clone_function(&self) -> Box<dyn CustomFunction> {
268        Box::new(self.clone())
269    }
270
271    fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
272        if args.len() != 1 {
273            bail!("PREDICATE() requires exactly 1 argument");
274        }
275
276        match &args[0] {
277            Value::String(s) if s.starts_with("<<") && s.ends_with(">>") => {
278                let inner = &s[2..s.len() - 2];
279                let parts: Vec<&str> = inner.split_whitespace().collect();
280                if parts.len() >= 3 {
281                    Ok(Value::Iri(parts[1].to_string()))
282                } else {
283                    bail!("Invalid quoted triple format");
284                }
285            }
286            _ => bail!("PREDICATE() requires a quoted triple argument"),
287        }
288    }
289}
290
291// ============================================================================
292// OBJECT Function - Extract object from quoted triple
293// ============================================================================
294
295/// OBJECT(?triple) - Extract object from a quoted triple
296///
297/// Returns the object component of an RDF-star quoted triple.
298///
299/// # Examples
300/// ```sparql
301/// OBJECT(<<:s :p :o>>) → :o
302/// ```
303#[derive(Debug, Clone)]
304pub struct ObjectFunction;
305
306impl CustomFunction for ObjectFunction {
307    fn name(&self) -> &str {
308        "http://www.w3.org/2001/sw/DataAccess/rq23#object"
309    }
310
311    fn arity(&self) -> Option<usize> {
312        Some(1)
313    }
314
315    fn parameter_types(&self) -> Vec<ValueType> {
316        vec![ValueType::Custom("QuotedTriple".to_string())]
317    }
318
319    fn return_type(&self) -> ValueType {
320        ValueType::Union(vec![
321            ValueType::Iri,
322            ValueType::BlankNode,
323            ValueType::Literal,
324            ValueType::Custom("QuotedTriple".to_string()),
325        ])
326    }
327
328    fn documentation(&self) -> &str {
329        "Extracts the object from a quoted triple (RDF-star)"
330    }
331
332    fn clone_function(&self) -> Box<dyn CustomFunction> {
333        Box::new(self.clone())
334    }
335
336    fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
337        if args.len() != 1 {
338            bail!("OBJECT() requires exactly 1 argument");
339        }
340
341        match &args[0] {
342            Value::String(s) if s.starts_with("<<") && s.ends_with(">>") => {
343                let inner = &s[2..s.len() - 2];
344                let parts: Vec<&str> = inner.split_whitespace().collect();
345                if parts.len() >= 3 {
346                    // Join remaining parts for the object (in case it has spaces)
347                    Ok(Value::Iri(parts[2..].join(" ")))
348                } else {
349                    bail!("Invalid quoted triple format");
350                }
351            }
352            _ => bail!("OBJECT() requires a quoted triple argument"),
353        }
354    }
355}
356
357// ============================================================================
358// isTRIPLE Function - Check if value is a quoted triple
359// ============================================================================
360
361/// isTRIPLE(?x) - Check if a value is a quoted triple
362///
363/// Returns true if the argument is an RDF-star quoted triple,
364/// false otherwise.
365///
366/// # Examples
367/// ```sparql
368/// isTRIPLE(<<:s :p :o>>) → true
369/// isTRIPLE(:s) → false
370/// ```
371#[derive(Debug, Clone)]
372pub struct IsTripleFunction;
373
374impl CustomFunction for IsTripleFunction {
375    fn name(&self) -> &str {
376        "http://www.w3.org/2001/sw/DataAccess/rq23#isTRIPLE"
377    }
378
379    fn arity(&self) -> Option<usize> {
380        Some(1)
381    }
382
383    fn parameter_types(&self) -> Vec<ValueType> {
384        vec![ValueType::Union(vec![
385            ValueType::Iri,
386            ValueType::BlankNode,
387            ValueType::Literal,
388            ValueType::Custom("QuotedTriple".to_string()),
389        ])]
390    }
391
392    fn return_type(&self) -> ValueType {
393        ValueType::Boolean
394    }
395
396    fn documentation(&self) -> &str {
397        "Returns true if the argument is a quoted triple (RDF-star), false otherwise"
398    }
399
400    fn clone_function(&self) -> Box<dyn CustomFunction> {
401        Box::new(self.clone())
402    }
403
404    fn execute(&self, args: &[Value], _context: &ExecutionContext) -> Result<Value> {
405        if args.len() != 1 {
406            bail!("isTRIPLE() requires exactly 1 argument");
407        }
408
409        let is_triple = match &args[0] {
410            Value::String(s) => s.starts_with("<<") && s.ends_with(">>"),
411            _ => false,
412        };
413
414        Ok(Value::Boolean(is_triple))
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    use super::*;
421    use std::collections::HashMap;
422
423    fn create_test_context() -> ExecutionContext {
424        ExecutionContext {
425            variables: HashMap::new(),
426            namespaces: HashMap::new(),
427            base_iri: None,
428            dataset_context: None,
429            query_time: chrono::Utc::now(),
430            optimization_level: crate::extensions::OptimizationLevel::None,
431            memory_limit: None,
432            time_limit: None,
433        }
434    }
435
436    #[test]
437    fn test_triple_function_construction() {
438        let func = TripleFunction;
439        let ctx = create_test_context();
440
441        let subject = Value::Iri("http://example.org/subject".to_string());
442        let predicate = Value::Iri("http://example.org/predicate".to_string());
443        let object = Value::Iri("http://example.org/object".to_string());
444
445        let result = func.execute(&[subject, predicate, object], &ctx);
446        assert!(result.is_ok());
447
448        if let Ok(Value::String(s)) = result {
449            assert!(s.starts_with("<<"));
450            assert!(s.ends_with(">>"));
451        } else {
452            panic!("Expected string result");
453        }
454    }
455
456    #[test]
457    fn test_triple_function_invalid_subject() {
458        let func = TripleFunction;
459        let ctx = create_test_context();
460
461        // Literal as subject should fail
462        let subject = Value::Literal {
463            value: "literal".to_string(),
464            language: None,
465            datatype: None,
466        };
467        let predicate = Value::Iri("http://example.org/predicate".to_string());
468        let object = Value::Iri("http://example.org/object".to_string());
469
470        let result = func.execute(&[subject, predicate, object], &ctx);
471        assert!(result.is_err());
472    }
473
474    #[test]
475    fn test_subject_function() {
476        let func = SubjectFunction;
477        let ctx = create_test_context();
478
479        let triple = Value::String(
480            "<<http://example.org/s http://example.org/p http://example.org/o>>".to_string(),
481        );
482
483        let result = func.execute(&[triple], &ctx);
484        assert!(result.is_ok());
485
486        if let Ok(Value::Iri(iri)) = result {
487            assert_eq!(iri, "http://example.org/s");
488        } else {
489            panic!("Expected IRI result");
490        }
491    }
492
493    #[test]
494    fn test_predicate_function() {
495        let func = PredicateFunction;
496        let ctx = create_test_context();
497
498        let triple = Value::String(
499            "<<http://example.org/s http://example.org/p http://example.org/o>>".to_string(),
500        );
501
502        let result = func.execute(&[triple], &ctx);
503        assert!(result.is_ok());
504
505        if let Ok(Value::Iri(iri)) = result {
506            assert_eq!(iri, "http://example.org/p");
507        } else {
508            panic!("Expected IRI result");
509        }
510    }
511
512    #[test]
513    fn test_object_function() {
514        let func = ObjectFunction;
515        let ctx = create_test_context();
516
517        let triple = Value::String(
518            "<<http://example.org/s http://example.org/p http://example.org/o>>".to_string(),
519        );
520
521        let result = func.execute(&[triple], &ctx);
522        assert!(result.is_ok());
523
524        if let Ok(Value::Iri(iri)) = result {
525            assert_eq!(iri, "http://example.org/o");
526        } else {
527            panic!("Expected IRI result");
528        }
529    }
530
531    #[test]
532    fn test_is_triple_function() {
533        let func = IsTripleFunction;
534        let ctx = create_test_context();
535
536        // Test with quoted triple
537        let triple = Value::String(
538            "<<http://example.org/s http://example.org/p http://example.org/o>>".to_string(),
539        );
540        let result = func.execute(&[triple], &ctx).unwrap();
541        assert_eq!(result, Value::Boolean(true));
542
543        // Test with non-triple
544        let non_triple = Value::Iri("http://example.org/resource".to_string());
545        let result = func.execute(&[non_triple], &ctx).unwrap();
546        assert_eq!(result, Value::Boolean(false));
547    }
548
549    #[test]
550    fn test_triple_function_arity() {
551        let func = TripleFunction;
552        assert_eq!(func.arity(), Some(3));
553        assert_eq!(
554            func.name(),
555            "http://www.w3.org/2001/sw/DataAccess/rq23#triple"
556        );
557    }
558
559    #[test]
560    fn test_accessor_function_arities() {
561        assert_eq!(SubjectFunction.arity(), Some(1));
562        assert_eq!(PredicateFunction.arity(), Some(1));
563        assert_eq!(ObjectFunction.arity(), Some(1));
564        assert_eq!(IsTripleFunction.arity(), Some(1));
565    }
566}