oxirs-core 0.2.4

Core RDF and SPARQL functionality for OxiRS - native Rust implementation with zero dependencies
Documentation
//! Pattern matching for RDF triples
//!
//! This module provides pattern matching functionality for querying RDF triples.

use crate::model::{BlankNode, Literal, NamedNode, Object, Predicate, Subject, Triple, Variable};

/// A pattern for matching triples
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TriplePattern {
    pub subject: Option<SubjectPattern>,
    pub predicate: Option<PredicatePattern>,
    pub object: Option<ObjectPattern>,
}

impl TriplePattern {
    /// Create a new triple pattern
    pub fn new(
        subject: Option<SubjectPattern>,
        predicate: Option<PredicatePattern>,
        object: Option<ObjectPattern>,
    ) -> Self {
        TriplePattern {
            subject,
            predicate,
            object,
        }
    }

    /// Get the subject pattern
    pub fn subject(&self) -> Option<&SubjectPattern> {
        self.subject.as_ref()
    }

    /// Get the predicate pattern
    pub fn predicate(&self) -> Option<&PredicatePattern> {
        self.predicate.as_ref()
    }

    /// Get the object pattern
    pub fn object(&self) -> Option<&ObjectPattern> {
        self.object.as_ref()
    }

    /// Check if a triple matches this pattern
    pub fn matches(&self, triple: &Triple) -> bool {
        // Check subject
        if let Some(ref subject_pattern) = self.subject {
            if !subject_pattern.matches(triple.subject()) {
                return false;
            }
        }

        // Check predicate
        if let Some(ref predicate_pattern) = self.predicate {
            if !predicate_pattern.matches(triple.predicate()) {
                return false;
            }
        }

        // Check object
        if let Some(ref object_pattern) = self.object {
            if !object_pattern.matches(triple.object()) {
                return false;
            }
        }

        true
    }
}

/// Pattern for matching subjects
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SubjectPattern {
    NamedNode(NamedNode),
    BlankNode(BlankNode),
    Variable(Variable),
}

impl SubjectPattern {
    /// Get the string representation
    pub fn as_str(&self) -> &str {
        match self {
            SubjectPattern::NamedNode(nn) => nn.as_str(),
            SubjectPattern::BlankNode(bn) => bn.as_str(),
            SubjectPattern::Variable(v) => v.as_str(),
        }
    }

    fn matches(&self, subject: &Subject) -> bool {
        match (self, subject) {
            (SubjectPattern::NamedNode(pn), Subject::NamedNode(sn)) => pn == sn,
            (SubjectPattern::BlankNode(pb), Subject::BlankNode(sb)) => pb == sb,
            (SubjectPattern::Variable(_), _) => true,
            _ => false,
        }
    }
}

/// Pattern for matching predicates
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PredicatePattern {
    NamedNode(NamedNode),
    Variable(Variable),
}

impl PredicatePattern {
    /// Get the string representation
    pub fn as_str(&self) -> &str {
        match self {
            PredicatePattern::NamedNode(nn) => nn.as_str(),
            PredicatePattern::Variable(v) => v.as_str(),
        }
    }

    fn matches(&self, predicate: &Predicate) -> bool {
        match (self, predicate) {
            (PredicatePattern::NamedNode(pn), Predicate::NamedNode(sn)) => pn == sn,
            (PredicatePattern::Variable(_), _) => true,
            _ => false,
        }
    }
}

/// Pattern for matching objects
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ObjectPattern {
    NamedNode(NamedNode),
    BlankNode(BlankNode),
    Literal(Literal),
    Variable(Variable),
}

impl ObjectPattern {
    /// Get the string representation
    pub fn as_str(&self) -> &str {
        match self {
            ObjectPattern::NamedNode(nn) => nn.as_str(),
            ObjectPattern::BlankNode(bn) => bn.as_str(),
            ObjectPattern::Literal(l) => l.value(),
            ObjectPattern::Variable(v) => v.as_str(),
        }
    }

    fn matches(&self, object: &Object) -> bool {
        match (self, object) {
            (ObjectPattern::NamedNode(pn), Object::NamedNode(on)) => pn == on,
            (ObjectPattern::BlankNode(pb), Object::BlankNode(ob)) => pb == ob,
            (ObjectPattern::Literal(pl), Object::Literal(ol)) => pl == ol,
            (ObjectPattern::Variable(_), _) => true,
            _ => false,
        }
    }
}

// Add From implementations for TermPattern conversions
use crate::query::algebra::TermPattern;

impl From<TermPattern> for SubjectPattern {
    fn from(term: TermPattern) -> Self {
        match term {
            TermPattern::NamedNode(n) => SubjectPattern::NamedNode(n),
            TermPattern::BlankNode(b) => SubjectPattern::BlankNode(b),
            TermPattern::Variable(v) => SubjectPattern::Variable(v),
            TermPattern::Literal(_) => panic!("Literals cannot be subjects"),
            TermPattern::QuotedTriple(_) => {
                panic!("RDF-star quoted triples as subjects not yet fully implemented")
            }
        }
    }
}

impl From<TermPattern> for PredicatePattern {
    fn from(term: TermPattern) -> Self {
        match term {
            TermPattern::NamedNode(n) => PredicatePattern::NamedNode(n),
            TermPattern::Variable(v) => PredicatePattern::Variable(v),
            TermPattern::BlankNode(_) => panic!("Blank nodes cannot be predicates"),
            TermPattern::Literal(_) => panic!("Literals cannot be predicates"),
            TermPattern::QuotedTriple(_) => panic!("Quoted triples cannot be predicates"),
        }
    }
}

impl From<TermPattern> for ObjectPattern {
    fn from(term: TermPattern) -> Self {
        match term {
            TermPattern::NamedNode(n) => ObjectPattern::NamedNode(n),
            TermPattern::BlankNode(b) => ObjectPattern::BlankNode(b),
            TermPattern::Literal(l) => ObjectPattern::Literal(l),
            TermPattern::Variable(v) => ObjectPattern::Variable(v),
            TermPattern::QuotedTriple(_) => {
                panic!("RDF-star quoted triples as objects not yet fully implemented")
            }
        }
    }
}

// TryFrom implementations for converting patterns to concrete terms
impl TryFrom<&SubjectPattern> for Subject {
    type Error = ();

    fn try_from(pattern: &SubjectPattern) -> Result<Self, Self::Error> {
        match pattern {
            SubjectPattern::NamedNode(n) => Ok(Subject::NamedNode(n.clone())),
            SubjectPattern::BlankNode(b) => Ok(Subject::BlankNode(b.clone())),
            SubjectPattern::Variable(_) => Err(()),
        }
    }
}

impl TryFrom<&PredicatePattern> for Predicate {
    type Error = ();

    fn try_from(pattern: &PredicatePattern) -> Result<Self, Self::Error> {
        match pattern {
            PredicatePattern::NamedNode(n) => Ok(Predicate::NamedNode(n.clone())),
            PredicatePattern::Variable(_) => Err(()),
        }
    }
}

impl TryFrom<&ObjectPattern> for Object {
    type Error = ();

    fn try_from(pattern: &ObjectPattern) -> Result<Self, Self::Error> {
        match pattern {
            ObjectPattern::NamedNode(n) => Ok(Object::NamedNode(n.clone())),
            ObjectPattern::BlankNode(b) => Ok(Object::BlankNode(b.clone())),
            ObjectPattern::Literal(l) => Ok(Object::Literal(l.clone())),
            ObjectPattern::Variable(_) => Err(()),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pattern_matching() {
        let subject = NamedNode::new("http://example.org/s").expect("valid IRI");
        let predicate = NamedNode::new("http://example.org/p").expect("valid IRI");
        let object = Literal::new("o");

        let triple = Triple::new(subject.clone(), predicate.clone(), object.clone());

        // Test exact match
        let pattern = TriplePattern::new(
            Some(SubjectPattern::NamedNode(subject.clone())),
            Some(PredicatePattern::NamedNode(predicate.clone())),
            Some(ObjectPattern::Literal(object.clone())),
        );
        assert!(pattern.matches(&triple));

        // Test wildcard match
        let pattern = TriplePattern::new(None, None, None);
        assert!(pattern.matches(&triple));

        // Test variable match
        let pattern = TriplePattern::new(
            Some(SubjectPattern::Variable(
                Variable::new("s").expect("valid variable name"),
            )),
            Some(PredicatePattern::Variable(
                Variable::new("p").expect("valid variable name"),
            )),
            Some(ObjectPattern::Variable(
                Variable::new("o").expect("valid variable name"),
            )),
        );
        assert!(pattern.matches(&triple));

        // Test non-match
        let different_subject = NamedNode::new("http://example.org/different").expect("valid IRI");
        let pattern = TriplePattern::new(
            Some(SubjectPattern::NamedNode(different_subject)),
            None,
            None,
        );
        assert!(!pattern.matches(&triple));
    }
}