Skip to main content

oxirs_arq/
term.rs

1//! Comprehensive Term System for SPARQL Query Processing
2//!
3//! This module provides a complete implementation of RDF terms with full datatype support,
4//! SPARQL-compliant comparison and ordering, variable binding, and expression evaluation.
5
6use crate::algebra::{Literal, Term as AlgebraTerm, TriplePattern, Variable};
7use crate::path::PropertyPath;
8use crate::total_float::{TotalF32, TotalF64};
9use anyhow::{anyhow, bail, Result};
10use base64::Engine;
11use chrono::{DateTime, Datelike, NaiveDate, NaiveTime, Timelike};
12use oxirs_core::model::NamedNode;
13use std::cmp::Ordering;
14use std::collections::HashMap;
15use std::fmt;
16
17/// XSD namespace for datatype URIs
18pub const XSD_NS: &str = "http://www.w3.org/2001/XMLSchema#";
19
20/// RDF namespace
21pub const RDF_NS: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
22
23/// Common XSD datatypes
24pub mod xsd {
25
26    pub const STRING: &str = "http://www.w3.org/2001/XMLSchema#string";
27    pub const BOOLEAN: &str = "http://www.w3.org/2001/XMLSchema#boolean";
28    pub const DECIMAL: &str = "http://www.w3.org/2001/XMLSchema#decimal";
29    pub const INTEGER: &str = "http://www.w3.org/2001/XMLSchema#integer";
30    pub const DOUBLE: &str = "http://www.w3.org/2001/XMLSchema#double";
31    pub const FLOAT: &str = "http://www.w3.org/2001/XMLSchema#float";
32    pub const DATE: &str = "http://www.w3.org/2001/XMLSchema#date";
33    pub const TIME: &str = "http://www.w3.org/2001/XMLSchema#time";
34    pub const DATE_TIME: &str = "http://www.w3.org/2001/XMLSchema#dateTime";
35    pub const DATE_TIME_STAMP: &str = "http://www.w3.org/2001/XMLSchema#dateTimeStamp";
36    pub const DURATION: &str = "http://www.w3.org/2001/XMLSchema#duration";
37    pub const BYTE: &str = "http://www.w3.org/2001/XMLSchema#byte";
38    pub const SHORT: &str = "http://www.w3.org/2001/XMLSchema#short";
39    pub const INT: &str = "http://www.w3.org/2001/XMLSchema#int";
40    pub const LONG: &str = "http://www.w3.org/2001/XMLSchema#long";
41    pub const UNSIGNED_BYTE: &str = "http://www.w3.org/2001/XMLSchema#unsignedByte";
42    pub const UNSIGNED_SHORT: &str = "http://www.w3.org/2001/XMLSchema#unsignedShort";
43    pub const UNSIGNED_INT: &str = "http://www.w3.org/2001/XMLSchema#unsignedInt";
44    pub const UNSIGNED_LONG: &str = "http://www.w3.org/2001/XMLSchema#unsignedLong";
45    pub const POSITIVE_INTEGER: &str = "http://www.w3.org/2001/XMLSchema#positiveInteger";
46    pub const NON_NEGATIVE_INTEGER: &str = "http://www.w3.org/2001/XMLSchema#nonNegativeInteger";
47    pub const NEGATIVE_INTEGER: &str = "http://www.w3.org/2001/XMLSchema#negativeInteger";
48    pub const NON_POSITIVE_INTEGER: &str = "http://www.w3.org/2001/XMLSchema#nonPositiveInteger";
49    pub const HEX_BINARY: &str = "http://www.w3.org/2001/XMLSchema#hexBinary";
50    pub const BASE64_BINARY: &str = "http://www.w3.org/2001/XMLSchema#base64Binary";
51}
52
53/// Enhanced RDF Term with full datatype support
54#[derive(Debug, Clone, PartialEq, Eq, Hash)]
55pub enum Term {
56    /// IRI reference
57    Iri(String),
58    /// Blank node
59    BlankNode(String),
60    /// Literal with proper datatype handling
61    Literal(LiteralValue),
62    /// Variable (for query patterns)
63    Variable(String),
64    /// Quoted triple (RDF-star support)
65    QuotedTriple(Box<QuotedTripleValue>),
66    /// Property path expression
67    PropertyPath(PropertyPath),
68}
69
70/// Quoted triple representation for RDF-star
71#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
72pub struct QuotedTripleValue {
73    pub subject: Term,
74    pub predicate: Term,
75    pub object: Term,
76}
77
78/// Literal value with parsed datatype
79#[derive(Debug, Clone, PartialEq, Eq, Hash)]
80pub struct LiteralValue {
81    /// Lexical form
82    pub lexical_form: String,
83    /// Datatype IRI
84    pub datatype: String,
85    /// Language tag (for language-tagged strings)
86    pub language_tag: Option<String>,
87    /// Parsed value (cached for efficiency)
88    parsed_value: ParsedValue,
89}
90
91/// Parsed literal values for efficient operations
92#[derive(Debug, Clone, PartialEq, Eq, Hash)]
93enum ParsedValue {
94    String(String),
95    Boolean(bool),
96    Integer(i64),
97    Decimal(TotalF64),
98    Float(TotalF32),
99    Double(TotalF64),
100    DateTime(i64), // Unix timestamp in nanoseconds
101    Date(i32),     // Days since epoch
102    Time(i64),     // Nanoseconds since midnight
103    #[allow(dead_code)]
104    Duration(i64), // Duration in nanoseconds
105    Binary(Vec<u8>),
106    Other,
107}
108
109impl Term {
110    /// Create an IRI term
111    pub fn iri(iri: &str) -> Self {
112        Term::Iri(iri.to_string())
113    }
114
115    /// Create a blank node term
116    pub fn blank_node(id: &str) -> Self {
117        Term::BlankNode(id.to_string())
118    }
119
120    /// Create a simple literal term
121    pub fn literal(value: &str) -> Self {
122        Term::Literal(LiteralValue::new_simple(value))
123    }
124
125    /// Create a typed literal term
126    pub fn typed_literal(value: &str, datatype: &str) -> Result<Self> {
127        Ok(Term::Literal(LiteralValue::new_typed(value, datatype)?))
128    }
129
130    /// Create a language-tagged literal
131    pub fn lang_literal(value: &str, lang: &str) -> Self {
132        Term::Literal(LiteralValue::new_lang(value, lang))
133    }
134
135    /// Create a variable term
136    pub fn variable(name: &str) -> Self {
137        Term::Variable(name.to_string())
138    }
139
140    /// Create a quoted triple term
141    pub fn quoted_triple(subject: Term, predicate: Term, object: Term) -> Self {
142        Term::QuotedTriple(Box::new(QuotedTripleValue {
143            subject,
144            predicate,
145            object,
146        }))
147    }
148
149    /// Create a property path term
150    pub fn property_path(path: PropertyPath) -> Self {
151        Term::PropertyPath(path)
152    }
153
154    /// Check if term is a variable
155    pub fn is_variable(&self) -> bool {
156        matches!(self, Term::Variable(_))
157    }
158
159    /// Check if term is ground (not a variable)
160    pub fn is_ground(&self) -> bool {
161        !self.is_variable()
162    }
163
164    /// Check if term is an IRI
165    pub fn is_iri(&self) -> bool {
166        matches!(self, Term::Iri(_))
167    }
168
169    /// Check if term is a blank node
170    pub fn is_blank_node(&self) -> bool {
171        matches!(self, Term::BlankNode(_))
172    }
173
174    /// Check if term is a literal
175    pub fn is_literal(&self) -> bool {
176        matches!(self, Term::Literal(_))
177    }
178
179    /// Check if term is a quoted triple
180    pub fn is_quoted_triple(&self) -> bool {
181        matches!(self, Term::QuotedTriple(_))
182    }
183
184    /// Check if term is a property path
185    pub fn is_property_path(&self) -> bool {
186        matches!(self, Term::PropertyPath(_))
187    }
188}
189
190impl LiteralValue {
191    /// Create a simple literal (xsd:string)
192    pub fn new_simple(value: &str) -> Self {
193        Self {
194            lexical_form: value.to_string(),
195            datatype: xsd::STRING.to_string(),
196            language_tag: None,
197            parsed_value: ParsedValue::String(value.to_string()),
198        }
199    }
200
201    /// Create a language-tagged literal
202    pub fn new_lang(value: &str, lang: &str) -> Self {
203        Self {
204            lexical_form: value.to_string(),
205            datatype: RDF_NS.to_string() + "langString",
206            language_tag: Some(lang.to_string()),
207            parsed_value: ParsedValue::String(value.to_string()),
208        }
209    }
210
211    /// Create a typed literal
212    pub fn new_typed(value: &str, datatype: &str) -> Result<Self> {
213        let parsed_value = Self::parse_value(value, datatype)?;
214        Ok(Self {
215            lexical_form: value.to_string(),
216            datatype: datatype.to_string(),
217            language_tag: None,
218            parsed_value,
219        })
220    }
221
222    /// Parse value according to datatype
223    fn parse_value(value: &str, datatype: &str) -> Result<ParsedValue> {
224        Ok(match datatype {
225            xsd::STRING => ParsedValue::String(value.to_string()),
226            xsd::BOOLEAN => {
227                let b = match value {
228                    "true" | "1" => true,
229                    "false" | "0" => false,
230                    _ => bail!("Invalid boolean value: {value}"),
231                };
232                ParsedValue::Boolean(b)
233            }
234            xsd::INTEGER | xsd::LONG | xsd::INT | xsd::SHORT | xsd::BYTE => {
235                let i = value
236                    .parse::<i64>()
237                    .map_err(|_| anyhow!("Invalid integer value: {value}"))?;
238                ParsedValue::Integer(i)
239            }
240            xsd::DECIMAL => {
241                let d = value
242                    .parse::<f64>()
243                    .map_err(|_| anyhow!("Invalid decimal value: {value}"))?;
244                ParsedValue::Decimal(TotalF64(d))
245            }
246            xsd::FLOAT => {
247                let f = value
248                    .parse::<f32>()
249                    .map_err(|_| anyhow!("Invalid float value: {value}"))?;
250                ParsedValue::Float(TotalF32(f))
251            }
252            xsd::DOUBLE => {
253                let d = value
254                    .parse::<f64>()
255                    .map_err(|_| anyhow!("Invalid double value: {value}"))?;
256                ParsedValue::Double(TotalF64(d))
257            }
258            xsd::DATE_TIME | xsd::DATE_TIME_STAMP => {
259                let dt = DateTime::parse_from_rfc3339(value)
260                    .map_err(|_| anyhow!("Invalid dateTime value: {value}"))?;
261                ParsedValue::DateTime(dt.timestamp_nanos_opt().unwrap_or(0))
262            }
263            xsd::DATE => {
264                let date = NaiveDate::parse_from_str(value, "%Y-%m-%d")
265                    .map_err(|_| anyhow!("Invalid date value: {value}"))?;
266                ParsedValue::Date(date.num_days_from_ce())
267            }
268            xsd::TIME => {
269                let time = NaiveTime::parse_from_str(value, "%H:%M:%S%.f")
270                    .map_err(|_| anyhow!("Invalid time value: {value}"))?;
271                ParsedValue::Time(
272                    time.num_seconds_from_midnight() as i64 * 1_000_000_000
273                        + time.nanosecond() as i64,
274                )
275            }
276            xsd::HEX_BINARY => {
277                let bytes =
278                    hex::decode(value).map_err(|_| anyhow!("Invalid hexBinary value: {value}"))?;
279                ParsedValue::Binary(bytes)
280            }
281            xsd::BASE64_BINARY => {
282                let bytes = base64::engine::general_purpose::STANDARD
283                    .decode(value)
284                    .map_err(|_| anyhow!("Invalid base64Binary value: {value}"))?;
285                ParsedValue::Binary(bytes)
286            }
287            _ => ParsedValue::Other,
288        })
289    }
290
291    /// Get effective boolean value
292    pub fn effective_boolean_value(&self) -> Result<bool> {
293        match &self.parsed_value {
294            ParsedValue::Boolean(b) => Ok(*b),
295            ParsedValue::String(s) => Ok(!s.is_empty()),
296            ParsedValue::Integer(i) => Ok(*i != 0),
297            ParsedValue::Decimal(d) => Ok(d.0 != 0.0),
298            ParsedValue::Float(f) => Ok(f.0 != 0.0),
299            ParsedValue::Double(d) => Ok(d.0 != 0.0),
300            _ => Ok(true),
301        }
302    }
303
304    /// Convert to numeric value
305    pub fn to_numeric(&self) -> Result<NumericValue> {
306        match &self.parsed_value {
307            ParsedValue::Integer(i) => Ok(NumericValue::Integer(*i)),
308            ParsedValue::Decimal(d) => Ok(NumericValue::Decimal(d.0)),
309            ParsedValue::Float(f) => Ok(NumericValue::Float(f.0 as f64)),
310            ParsedValue::Double(d) => Ok(NumericValue::Double(d.0)),
311            ParsedValue::Boolean(b) => Ok(NumericValue::Integer(if *b { 1 } else { 0 })),
312            ParsedValue::String(s) => {
313                // Try parsing as number
314                if let Ok(i) = s.parse::<i64>() {
315                    Ok(NumericValue::Integer(i))
316                } else if let Ok(d) = s.parse::<f64>() {
317                    Ok(NumericValue::Double(d))
318                } else {
319                    bail!("Cannot convert string '{s}' to numeric")
320                }
321            }
322            _ => bail!("Cannot convert {dt} to numeric", dt = self.datatype),
323        }
324    }
325
326    /// Check if literal is numeric
327    pub fn is_numeric(&self) -> bool {
328        matches!(
329            &self.parsed_value,
330            ParsedValue::Integer(_)
331                | ParsedValue::Decimal(_)
332                | ParsedValue::Float(_)
333                | ParsedValue::Double(_)
334        )
335    }
336}
337
338/// Numeric values for arithmetic operations
339#[derive(Debug, Clone, PartialEq)]
340pub enum NumericValue {
341    Integer(i64),
342    Decimal(f64),
343    Float(f64),
344    Double(f64),
345}
346
347impl NumericValue {
348    /// Promote to common numeric type
349    pub fn promote_with(&self, other: &NumericValue) -> (NumericValue, NumericValue) {
350        use NumericValue::*;
351        match (self, other) {
352            (Integer(a), Integer(b)) => (Integer(*a), Integer(*b)),
353            (Integer(a), Decimal(b)) => (Decimal(*a as f64), Decimal(*b)),
354            (Integer(a), Float(b)) => (Float(*a as f64), Float(*b)),
355            (Integer(a), Double(b)) => (Double(*a as f64), Double(*b)),
356            (Decimal(a), Integer(b)) => (Decimal(*a), Decimal(*b as f64)),
357            (Decimal(a), Decimal(b)) => (Decimal(*a), Decimal(*b)),
358            (Decimal(a), Float(b)) => (Float(*a), Float(*b)),
359            (Decimal(a), Double(b)) => (Double(*a), Double(*b)),
360            (Float(a), Integer(b)) => (Float(*a), Float(*b as f64)),
361            (Float(a), Decimal(b)) => (Float(*a), Float(*b)),
362            (Float(a), Float(b)) => (Float(*a), Float(*b)),
363            (Float(a), Double(b)) => (Double(*a), Double(*b)),
364            (Double(a), Integer(b)) => (Double(*a), Double(*b as f64)),
365            (Double(a), Decimal(b)) => (Double(*a), Double(*b)),
366            (Double(a), Float(b)) => (Double(*a), Double(*b)),
367            (Double(a), Double(b)) => (Double(*a), Double(*b)),
368        }
369    }
370
371    /// Convert back to term
372    pub fn to_term(&self) -> Term {
373        match self {
374            NumericValue::Integer(i) => Term::typed_literal(&i.to_string(), xsd::INTEGER)
375                .expect("integer to xsd:integer literal should always succeed"),
376            NumericValue::Decimal(d) => Term::typed_literal(&d.to_string(), xsd::DECIMAL)
377                .expect("decimal to xsd:decimal literal should always succeed"),
378            NumericValue::Float(f) => Term::typed_literal(&f.to_string(), xsd::FLOAT)
379                .expect("float to xsd:float literal should always succeed"),
380            NumericValue::Double(d) => Term::typed_literal(&d.to_string(), xsd::DOUBLE)
381                .expect("double to xsd:double literal should always succeed"),
382        }
383    }
384}
385
386/// SPARQL value ordering according to spec
387impl PartialOrd for Term {
388    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
389        Some(self.cmp(other))
390    }
391}
392
393impl Ord for Term {
394    fn cmp(&self, other: &Self) -> Ordering {
395        use Term::*;
396
397        // Order: Variable < BlankNode < Iri < Literal < QuotedTriple < PropertyPath
398        match (self, other) {
399            (Variable(_), Variable(_)) => Ordering::Equal,
400            (Variable(_), _) => Ordering::Less,
401            (_, Variable(_)) => Ordering::Greater,
402
403            (BlankNode(a), BlankNode(b)) => a.cmp(b),
404            (BlankNode(_), _) => Ordering::Less,
405            (_, BlankNode(_)) => Ordering::Greater,
406
407            (Iri(a), Iri(b)) => a.cmp(b),
408            (Iri(_), Literal(_) | QuotedTriple(_) | PropertyPath(_)) => Ordering::Less,
409            (Literal(_) | QuotedTriple(_) | PropertyPath(_), Iri(_)) => Ordering::Greater,
410
411            (Literal(a), Literal(b)) => a.cmp(b),
412            (Literal(_), QuotedTriple(_) | PropertyPath(_)) => Ordering::Less,
413            (QuotedTriple(_) | PropertyPath(_), Literal(_)) => Ordering::Greater,
414
415            (QuotedTriple(a), QuotedTriple(b)) => a.cmp(b),
416            (QuotedTriple(_), PropertyPath(_)) => Ordering::Less,
417            (PropertyPath(_), QuotedTriple(_)) => Ordering::Greater,
418
419            (PropertyPath(a), PropertyPath(b)) => a.cmp(b),
420        }
421    }
422}
423
424impl PartialOrd for LiteralValue {
425    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
426        Some(self.cmp(other))
427    }
428}
429
430impl Ord for LiteralValue {
431    fn cmp(&self, other: &Self) -> Ordering {
432        // Language tags take precedence
433        match (&self.language_tag, &other.language_tag) {
434            (Some(a), Some(b)) => match a.cmp(b) {
435                Ordering::Equal => self.lexical_form.cmp(&other.lexical_form),
436                ord => ord,
437            },
438            (Some(_), None) => Ordering::Less,
439            (None, Some(_)) => Ordering::Greater,
440            (None, None) => {
441                // Compare by datatype, then by value
442                match self.datatype.cmp(&other.datatype) {
443                    Ordering::Equal => self.compare_same_datatype(other),
444                    ord => ord,
445                }
446            }
447        }
448    }
449}
450
451impl LiteralValue {
452    fn compare_same_datatype(&self, other: &Self) -> Ordering {
453        match (&self.parsed_value, &other.parsed_value) {
454            (ParsedValue::Boolean(a), ParsedValue::Boolean(b)) => a.cmp(b),
455            (ParsedValue::Integer(a), ParsedValue::Integer(b)) => a.cmp(b),
456            (ParsedValue::Decimal(a), ParsedValue::Decimal(b)) => a.cmp(b),
457            (ParsedValue::Float(a), ParsedValue::Float(b)) => a.cmp(b),
458            (ParsedValue::Double(a), ParsedValue::Double(b)) => a.cmp(b),
459            (ParsedValue::DateTime(a), ParsedValue::DateTime(b)) => a.cmp(b),
460            (ParsedValue::Date(a), ParsedValue::Date(b)) => a.cmp(b),
461            (ParsedValue::Time(a), ParsedValue::Time(b)) => a.cmp(b),
462            (ParsedValue::Duration(a), ParsedValue::Duration(b)) => a.cmp(b),
463            (ParsedValue::Binary(a), ParsedValue::Binary(b)) => a.cmp(b),
464            _ => self.lexical_form.cmp(&other.lexical_form),
465        }
466    }
467}
468
469/// Variable binding context
470#[derive(Debug, Clone, Default)]
471pub struct BindingContext {
472    /// Current variable bindings
473    bindings: HashMap<Variable, Term>,
474    /// Nested scopes for subqueries
475    scopes: Vec<HashMap<Variable, Term>>,
476}
477
478impl BindingContext {
479    /// Create new binding context
480    pub fn new() -> Self {
481        Self::default()
482    }
483
484    /// Bind a variable to a term
485    pub fn bind(&mut self, var: &str, term: Term) {
486        if let Ok(variable) = Variable::new(var) {
487            self.bindings.insert(variable, term);
488        }
489    }
490
491    /// Get binding for a variable
492    pub fn get(&self, var: &str) -> Option<&Term> {
493        // Check current scope first
494        if let Ok(variable) = Variable::new(var) {
495            if let Some(term) = self.bindings.get(&variable) {
496                return Some(term);
497            }
498        }
499
500        // Check parent scopes
501        for scope in self.scopes.iter().rev() {
502            if let Ok(variable) = Variable::new(var) {
503                if let Some(term) = scope.get(&variable) {
504                    return Some(term);
505                }
506            }
507        }
508
509        None
510    }
511
512    /// Check if variable is bound
513    pub fn is_bound(&self, var: &str) -> bool {
514        self.get(var).is_some()
515    }
516
517    /// Push new scope
518    pub fn push_scope(&mut self) {
519        let current = std::mem::take(&mut self.bindings);
520        self.scopes.push(current);
521    }
522
523    /// Pop scope
524    pub fn pop_scope(&mut self) {
525        if let Some(scope) = self.scopes.pop() {
526            self.bindings = scope;
527        }
528    }
529
530    /// Get all bound variables
531    pub fn variables(&self) -> Vec<&str> {
532        let mut vars: Vec<_> = self.bindings.keys().map(|s| s.as_str()).collect();
533
534        for scope in &self.scopes {
535            for var in scope.keys() {
536                if !vars.contains(&var.as_str()) {
537                    vars.push(var.as_str());
538                }
539            }
540        }
541
542        vars
543    }
544
545    /// Apply bindings to a term
546    pub fn apply(&self, term: &Term) -> Term {
547        match term {
548            Term::Variable(var) => self.get(var).cloned().unwrap_or_else(|| term.clone()),
549            _ => term.clone(),
550        }
551    }
552}
553
554/// Term pattern matching
555pub fn matches_pattern(pattern: &Term, term: &Term, bindings: &mut BindingContext) -> bool {
556    match (pattern, term) {
557        (Term::Variable(var), _) => {
558            // Check if variable is already bound
559            if let Some(bound) = bindings.get(var) {
560                bound == term
561            } else {
562                // Bind variable
563                bindings.bind(var, term.clone());
564                true
565            }
566        }
567        (Term::Iri(p), Term::Iri(t)) => p == t,
568        (Term::BlankNode(p), Term::BlankNode(t)) => p == t,
569        (Term::Literal(p), Term::Literal(t)) => p == t,
570        (Term::QuotedTriple(p), Term::QuotedTriple(t)) => {
571            matches_pattern(&p.subject, &t.subject, bindings)
572                && matches_pattern(&p.predicate, &t.predicate, bindings)
573                && matches_pattern(&p.object, &t.object, bindings)
574        }
575        (Term::PropertyPath(p), Term::PropertyPath(t)) => p == t,
576        _ => false,
577    }
578}
579
580impl fmt::Display for Term {
581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582        match self {
583            Term::Iri(iri) => write!(f, "<{iri}>"),
584            Term::BlankNode(id) => write!(f, "_{id}"),
585            Term::Literal(lit) => write!(f, "{lit}"),
586            Term::Variable(var) => write!(f, "?{var}"),
587            Term::QuotedTriple(triple) => {
588                write!(
589                    f,
590                    "<<{} {} {}>>",
591                    triple.subject, triple.predicate, triple.object
592                )
593            }
594            Term::PropertyPath(path) => write!(f, "{path}"),
595        }
596    }
597}
598
599impl fmt::Display for LiteralValue {
600    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
601        write!(f, "\"{}\"", self.lexical_form)?;
602        if let Some(lang) = &self.language_tag {
603            write!(f, "@{lang}")?;
604        } else if self.datatype != xsd::STRING {
605            write!(f, "^^<{dt}>", dt = self.datatype)?;
606        }
607        Ok(())
608    }
609}
610
611impl Term {
612    /// Convert from algebra::Term to term::Term
613    pub fn from_algebra_term(algebra_term: &AlgebraTerm) -> Self {
614        match algebra_term {
615            AlgebraTerm::Iri(iri) => Term::iri(iri.as_str()),
616            AlgebraTerm::Literal(lit) => {
617                if let Some(lang) = &lit.language {
618                    Term::lang_literal(&lit.value, lang)
619                } else if let Some(datatype) = &lit.datatype {
620                    Term::typed_literal(&lit.value, datatype.as_str())
621                        .unwrap_or_else(|_| Term::literal(&lit.value))
622                } else {
623                    Term::literal(&lit.value)
624                }
625            }
626            AlgebraTerm::BlankNode(bn) => Term::blank_node(bn),
627            AlgebraTerm::Variable(var) => Term::variable(var.as_str()),
628            AlgebraTerm::QuotedTriple(quoted_triple) => {
629                Term::QuotedTriple(Box::new(QuotedTripleValue {
630                    subject: Term::from_algebra_term(&quoted_triple.subject),
631                    predicate: Term::from_algebra_term(&quoted_triple.predicate),
632                    object: Term::from_algebra_term(&quoted_triple.object),
633                }))
634            }
635            AlgebraTerm::PropertyPath(path) => {
636                Term::PropertyPath(Self::convert_algebra_to_path_property_path(path))
637            }
638        }
639    }
640
641    /// Convert from term::Term to algebra::Term
642    pub fn to_algebra_term(&self) -> AlgebraTerm {
643        match self {
644            Term::Iri(iri) => AlgebraTerm::Iri(NamedNode::new_unchecked(iri)),
645            Term::BlankNode(bn) => AlgebraTerm::BlankNode(bn.clone()),
646            Term::Literal(lit_val) => AlgebraTerm::Literal(Literal {
647                value: lit_val.lexical_form.clone(),
648                language: lit_val.language_tag.clone(),
649                datatype: if lit_val.datatype != xsd::STRING {
650                    Some(NamedNode::new_unchecked(&lit_val.datatype))
651                } else {
652                    None
653                },
654            }),
655            Term::Variable(var) => AlgebraTerm::Variable(
656                Variable::new(var).expect("variable name should be valid for algebra conversion"),
657            ),
658            Term::QuotedTriple(triple) => AlgebraTerm::QuotedTriple(Box::new(TriplePattern {
659                subject: triple.subject.to_algebra_term(),
660                predicate: triple.predicate.to_algebra_term(),
661                object: triple.object.to_algebra_term(),
662            })),
663            Term::PropertyPath(path) => {
664                AlgebraTerm::PropertyPath(Self::convert_path_to_algebra_property_path(path))
665            }
666        }
667    }
668
669    /// Check if this term represents a truthy value for SPARQL evaluation
670    pub fn effective_boolean_value(&self) -> Result<bool> {
671        match self {
672            Term::Literal(lit_val) => match lit_val.datatype.as_str() {
673                xsd::BOOLEAN => Ok(lit_val.lexical_form == "true" || lit_val.lexical_form == "1"),
674                xsd::STRING => Ok(!lit_val.lexical_form.is_empty()),
675                dt if dt.starts_with(XSD_NS)
676                    && (dt.ends_with("integer")
677                        || dt.ends_with("decimal")
678                        || dt.ends_with("double")
679                        || dt.ends_with("float")) =>
680                {
681                    let val = lit_val.lexical_form.parse::<f64>().unwrap_or(0.0);
682                    Ok(val != 0.0 && !val.is_nan())
683                }
684                _ => Ok(!lit_val.lexical_form.is_empty()),
685            },
686            Term::Iri(_) | Term::BlankNode(_) | Term::QuotedTriple(_) | Term::PropertyPath(_) => {
687                Ok(true)
688            }
689            Term::Variable(_) => bail!("Cannot evaluate variable as boolean"),
690        }
691    }
692
693    /// Convert term to numeric value
694    pub fn to_numeric(&self) -> Result<NumericValue> {
695        match self {
696            Term::Literal(lit_val) => lit_val.to_numeric(),
697            Term::Iri(_) | Term::BlankNode(_) | Term::QuotedTriple(_) | Term::PropertyPath(_) => {
698                bail!("Cannot convert IRI, blank node, quoted triple, or property path to numeric")
699            }
700            Term::Variable(_) => bail!("Cannot convert unbound variable to numeric"),
701        }
702    }
703
704    /// Convert algebra PropertyPath to path PropertyPath
705    fn convert_algebra_to_path_property_path(
706        algebra_path: &crate::algebra::PropertyPath,
707    ) -> crate::path::PropertyPath {
708        use crate::algebra::PropertyPath as AlgPath;
709        use crate::algebra::Term as AlgTerm;
710        use crate::path::PropertyPath as PathPath;
711
712        match algebra_path {
713            AlgPath::Iri(iri) => PathPath::Direct(AlgTerm::Iri(iri.clone())),
714            AlgPath::Variable(var) => PathPath::Direct(AlgTerm::Variable(var.clone())),
715            AlgPath::Inverse(path) => {
716                PathPath::Inverse(Box::new(Self::convert_algebra_to_path_property_path(path)))
717            }
718            AlgPath::Sequence(left, right) => PathPath::Sequence(
719                Box::new(Self::convert_algebra_to_path_property_path(left)),
720                Box::new(Self::convert_algebra_to_path_property_path(right)),
721            ),
722            AlgPath::Alternative(left, right) => PathPath::Alternative(
723                Box::new(Self::convert_algebra_to_path_property_path(left)),
724                Box::new(Self::convert_algebra_to_path_property_path(right)),
725            ),
726            AlgPath::ZeroOrMore(path) => {
727                PathPath::ZeroOrMore(Box::new(Self::convert_algebra_to_path_property_path(path)))
728            }
729            AlgPath::OneOrMore(path) => {
730                PathPath::OneOrMore(Box::new(Self::convert_algebra_to_path_property_path(path)))
731            }
732            AlgPath::ZeroOrOne(path) => {
733                PathPath::ZeroOrOne(Box::new(Self::convert_algebra_to_path_property_path(path)))
734            }
735            AlgPath::NegatedPropertySet(_) => {
736                // For now, convert negated property sets to a simple Direct path
737                // This is a simplification and might need better handling
738                PathPath::Direct(AlgTerm::Iri(oxirs_core::model::NamedNode::new_unchecked(
739                    "<urn:negated-property-set>",
740                )))
741            }
742        }
743    }
744
745    /// Convert path PropertyPath to algebra PropertyPath
746    fn convert_path_to_algebra_property_path(
747        path_path: &crate::path::PropertyPath,
748    ) -> crate::algebra::PropertyPath {
749        use crate::algebra::PropertyPath as AlgPath;
750        use crate::algebra::Term as AlgTerm;
751        use crate::path::PropertyPath as PathPath;
752
753        match path_path {
754            PathPath::Direct(term) => match term {
755                AlgTerm::Iri(iri) => {
756                    AlgPath::Iri(oxirs_core::model::NamedNode::new_unchecked(iri.as_str()))
757                }
758                AlgTerm::Variable(var) => AlgPath::Variable(var.clone()),
759                _ => AlgPath::Iri(oxirs_core::model::NamedNode::new_unchecked("<urn:unknown>")),
760            },
761            PathPath::Inverse(path) => {
762                AlgPath::Inverse(Box::new(Self::convert_path_to_algebra_property_path(path)))
763            }
764            PathPath::Sequence(left, right) => AlgPath::Sequence(
765                Box::new(Self::convert_path_to_algebra_property_path(left)),
766                Box::new(Self::convert_path_to_algebra_property_path(right)),
767            ),
768            PathPath::Alternative(left, right) => AlgPath::Alternative(
769                Box::new(Self::convert_path_to_algebra_property_path(left)),
770                Box::new(Self::convert_path_to_algebra_property_path(right)),
771            ),
772            PathPath::ZeroOrMore(path) => {
773                AlgPath::ZeroOrMore(Box::new(Self::convert_path_to_algebra_property_path(path)))
774            }
775            PathPath::OneOrMore(path) => {
776                AlgPath::OneOrMore(Box::new(Self::convert_path_to_algebra_property_path(path)))
777            }
778            PathPath::ZeroOrOne(path) => {
779                AlgPath::ZeroOrOne(Box::new(Self::convert_path_to_algebra_property_path(path)))
780            }
781            PathPath::NegatedPropertySet(terms) => {
782                // Convert terms to PropertyPaths for the negated property set
783                let property_paths: Vec<crate::algebra::PropertyPath> = terms
784                    .iter()
785                    .filter_map(|term| match term {
786                        AlgTerm::Iri(iri) => Some(AlgPath::Iri(iri.clone())),
787                        AlgTerm::Variable(var) => Some(AlgPath::Variable(var.clone())),
788                        _ => None,
789                    })
790                    .collect();
791                AlgPath::NegatedPropertySet(property_paths)
792            }
793        }
794    }
795}
796
797#[cfg(test)]
798mod tests {
799    use super::*;
800
801    #[test]
802    fn test_term_creation() {
803        let iri = Term::iri("http://example.org/foo");
804        assert!(iri.is_iri());
805
806        let blank = Term::blank_node("b1");
807        assert!(blank.is_blank_node());
808
809        let lit = Term::literal("hello");
810        assert!(lit.is_literal());
811
812        let var = Term::variable("x");
813        assert!(var.is_variable());
814    }
815
816    #[test]
817    fn test_typed_literals() {
818        let int_lit = Term::typed_literal("42", xsd::INTEGER).unwrap();
819        assert!(matches!(int_lit, Term::Literal(_)));
820
821        let bool_lit = Term::typed_literal("true", xsd::BOOLEAN).unwrap();
822        assert!(bool_lit.effective_boolean_value().unwrap());
823
824        let date_lit = Term::typed_literal("2023-01-01", xsd::DATE).unwrap();
825        assert!(matches!(date_lit, Term::Literal(_)));
826    }
827
828    #[test]
829    fn test_numeric_conversion() {
830        let int_term = Term::typed_literal("42", xsd::INTEGER).unwrap();
831        let num = int_term.to_numeric().unwrap();
832        assert_eq!(num, NumericValue::Integer(42));
833
834        let float_term = Term::typed_literal("3.14", xsd::FLOAT).unwrap();
835        let num = float_term.to_numeric().unwrap();
836        assert!(matches!(num, NumericValue::Float(_)));
837    }
838
839    #[test]
840    fn test_term_ordering() {
841        let var = Term::variable("x");
842        let blank = Term::blank_node("b1");
843        let iri = Term::iri("http://example.org");
844        let lit = Term::literal("test");
845
846        assert!(var < blank);
847        assert!(blank < iri);
848        assert!(iri < lit);
849    }
850
851    #[test]
852    fn test_binding_context() {
853        let mut ctx = BindingContext::new();
854
855        let term = Term::literal("value");
856        ctx.bind("x", term.clone());
857
858        assert!(ctx.is_bound("x"));
859        assert_eq!(ctx.get("x"), Some(&term));
860
861        ctx.push_scope();
862        ctx.bind("y", Term::literal("other"));
863
864        assert!(ctx.is_bound("x")); // Still visible
865        assert!(ctx.is_bound("y"));
866
867        ctx.pop_scope();
868        assert!(ctx.is_bound("x"));
869        assert!(!ctx.is_bound("y")); // No longer visible
870    }
871
872    #[test]
873    fn test_pattern_matching() {
874        let mut ctx = BindingContext::new();
875
876        let pattern = Term::variable("x");
877        let term = Term::literal("test");
878
879        assert!(matches_pattern(&pattern, &term, &mut ctx));
880        assert_eq!(ctx.get("x"), Some(&term));
881
882        // Second match with same variable should check equality
883        let term2 = Term::literal("other");
884        assert!(!matches_pattern(&pattern, &term2, &mut ctx));
885    }
886
887    #[test]
888    fn test_quoted_triple_term() {
889        let subject = Term::iri("http://example.org/subject");
890        let predicate = Term::iri("http://example.org/predicate");
891        let object = Term::literal("object");
892
893        let quoted_triple = Term::quoted_triple(subject.clone(), predicate.clone(), object.clone());
894        assert!(quoted_triple.is_quoted_triple());
895
896        // Test display format
897        let display = format!("{quoted_triple}");
898        assert!(display.starts_with("<<"));
899        assert!(display.ends_with(">>"));
900    }
901
902    #[test]
903    fn test_property_path_term() {
904        use crate::path::PropertyPath;
905
906        let direct_path = PropertyPath::Direct(crate::algebra::Term::Iri(
907            crate::algebra::Iri::new_unchecked("http://example.org/prop"),
908        ));
909        let path_term = Term::property_path(direct_path);
910        assert!(path_term.is_property_path());
911    }
912
913    #[test]
914    fn test_term_ordering_with_new_variants() {
915        let var = Term::variable("x");
916        let blank = Term::blank_node("b1");
917        let iri = Term::iri("http://example.org");
918        let lit = Term::literal("test");
919        let quoted = Term::quoted_triple(
920            Term::iri("http://example.org/s"),
921            Term::iri("http://example.org/p"),
922            Term::iri("http://example.org/o"),
923        );
924        let path = Term::property_path(crate::path::PropertyPath::Direct(
925            crate::algebra::Term::Iri(crate::algebra::Iri::new_unchecked(
926                "http://example.org/prop",
927            )),
928        ));
929
930        // Test ordering: Variable < BlankNode < Iri < Literal < QuotedTriple < PropertyPath
931        assert!(var < blank);
932        assert!(blank < iri);
933        assert!(iri < lit);
934        assert!(lit < quoted);
935        assert!(quoted < path);
936    }
937}