Skip to main content

oxirs_core/model/
pattern.rs

1//! Pattern matching for RDF triples
2//!
3//! This module provides pattern matching functionality for querying RDF triples.
4
5use crate::model::{BlankNode, Literal, NamedNode, Object, Predicate, Subject, Triple, Variable};
6
7/// A pattern for matching triples
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub struct TriplePattern {
11    pub subject: Option<SubjectPattern>,
12    pub predicate: Option<PredicatePattern>,
13    pub object: Option<ObjectPattern>,
14}
15
16impl TriplePattern {
17    /// Create a new triple pattern
18    pub fn new(
19        subject: Option<SubjectPattern>,
20        predicate: Option<PredicatePattern>,
21        object: Option<ObjectPattern>,
22    ) -> Self {
23        TriplePattern {
24            subject,
25            predicate,
26            object,
27        }
28    }
29
30    /// Get the subject pattern
31    pub fn subject(&self) -> Option<&SubjectPattern> {
32        self.subject.as_ref()
33    }
34
35    /// Get the predicate pattern
36    pub fn predicate(&self) -> Option<&PredicatePattern> {
37        self.predicate.as_ref()
38    }
39
40    /// Get the object pattern
41    pub fn object(&self) -> Option<&ObjectPattern> {
42        self.object.as_ref()
43    }
44
45    /// Check if a triple matches this pattern
46    pub fn matches(&self, triple: &Triple) -> bool {
47        // Check subject
48        if let Some(ref subject_pattern) = self.subject {
49            if !subject_pattern.matches(triple.subject()) {
50                return false;
51            }
52        }
53
54        // Check predicate
55        if let Some(ref predicate_pattern) = self.predicate {
56            if !predicate_pattern.matches(triple.predicate()) {
57                return false;
58            }
59        }
60
61        // Check object
62        if let Some(ref object_pattern) = self.object {
63            if !object_pattern.matches(triple.object()) {
64                return false;
65            }
66        }
67
68        true
69    }
70}
71
72/// Pattern for matching subjects
73#[derive(Debug, Clone, PartialEq, Eq, Hash)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75pub enum SubjectPattern {
76    NamedNode(NamedNode),
77    BlankNode(BlankNode),
78    Variable(Variable),
79}
80
81impl SubjectPattern {
82    /// Get the string representation
83    pub fn as_str(&self) -> &str {
84        match self {
85            SubjectPattern::NamedNode(nn) => nn.as_str(),
86            SubjectPattern::BlankNode(bn) => bn.as_str(),
87            SubjectPattern::Variable(v) => v.as_str(),
88        }
89    }
90
91    fn matches(&self, subject: &Subject) -> bool {
92        match (self, subject) {
93            (SubjectPattern::NamedNode(pn), Subject::NamedNode(sn)) => pn == sn,
94            (SubjectPattern::BlankNode(pb), Subject::BlankNode(sb)) => pb == sb,
95            (SubjectPattern::Variable(_), _) => true,
96            _ => false,
97        }
98    }
99}
100
101/// Pattern for matching predicates
102#[derive(Debug, Clone, PartialEq, Eq, Hash)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104pub enum PredicatePattern {
105    NamedNode(NamedNode),
106    Variable(Variable),
107}
108
109impl PredicatePattern {
110    /// Get the string representation
111    pub fn as_str(&self) -> &str {
112        match self {
113            PredicatePattern::NamedNode(nn) => nn.as_str(),
114            PredicatePattern::Variable(v) => v.as_str(),
115        }
116    }
117
118    fn matches(&self, predicate: &Predicate) -> bool {
119        match (self, predicate) {
120            (PredicatePattern::NamedNode(pn), Predicate::NamedNode(sn)) => pn == sn,
121            (PredicatePattern::Variable(_), _) => true,
122            _ => false,
123        }
124    }
125}
126
127/// Pattern for matching objects
128#[derive(Debug, Clone, PartialEq, Eq, Hash)]
129#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
130pub enum ObjectPattern {
131    NamedNode(NamedNode),
132    BlankNode(BlankNode),
133    Literal(Literal),
134    Variable(Variable),
135}
136
137impl ObjectPattern {
138    /// Get the string representation
139    pub fn as_str(&self) -> &str {
140        match self {
141            ObjectPattern::NamedNode(nn) => nn.as_str(),
142            ObjectPattern::BlankNode(bn) => bn.as_str(),
143            ObjectPattern::Literal(l) => l.value(),
144            ObjectPattern::Variable(v) => v.as_str(),
145        }
146    }
147
148    fn matches(&self, object: &Object) -> bool {
149        match (self, object) {
150            (ObjectPattern::NamedNode(pn), Object::NamedNode(on)) => pn == on,
151            (ObjectPattern::BlankNode(pb), Object::BlankNode(ob)) => pb == ob,
152            (ObjectPattern::Literal(pl), Object::Literal(ol)) => pl == ol,
153            (ObjectPattern::Variable(_), _) => true,
154            _ => false,
155        }
156    }
157}
158
159// Add From implementations for TermPattern conversions
160use crate::query::algebra::TermPattern;
161
162impl From<TermPattern> for SubjectPattern {
163    fn from(term: TermPattern) -> Self {
164        match term {
165            TermPattern::NamedNode(n) => SubjectPattern::NamedNode(n),
166            TermPattern::BlankNode(b) => SubjectPattern::BlankNode(b),
167            TermPattern::Variable(v) => SubjectPattern::Variable(v),
168            TermPattern::Literal(_) => panic!("Literals cannot be subjects"),
169            TermPattern::QuotedTriple(_) => {
170                panic!("RDF-star quoted triples as subjects not yet fully implemented")
171            }
172        }
173    }
174}
175
176impl From<TermPattern> for PredicatePattern {
177    fn from(term: TermPattern) -> Self {
178        match term {
179            TermPattern::NamedNode(n) => PredicatePattern::NamedNode(n),
180            TermPattern::Variable(v) => PredicatePattern::Variable(v),
181            TermPattern::BlankNode(_) => panic!("Blank nodes cannot be predicates"),
182            TermPattern::Literal(_) => panic!("Literals cannot be predicates"),
183            TermPattern::QuotedTriple(_) => panic!("Quoted triples cannot be predicates"),
184        }
185    }
186}
187
188impl From<TermPattern> for ObjectPattern {
189    fn from(term: TermPattern) -> Self {
190        match term {
191            TermPattern::NamedNode(n) => ObjectPattern::NamedNode(n),
192            TermPattern::BlankNode(b) => ObjectPattern::BlankNode(b),
193            TermPattern::Literal(l) => ObjectPattern::Literal(l),
194            TermPattern::Variable(v) => ObjectPattern::Variable(v),
195            TermPattern::QuotedTriple(_) => {
196                panic!("RDF-star quoted triples as objects not yet fully implemented")
197            }
198        }
199    }
200}
201
202// TryFrom implementations for converting patterns to concrete terms
203impl TryFrom<&SubjectPattern> for Subject {
204    type Error = ();
205
206    fn try_from(pattern: &SubjectPattern) -> Result<Self, Self::Error> {
207        match pattern {
208            SubjectPattern::NamedNode(n) => Ok(Subject::NamedNode(n.clone())),
209            SubjectPattern::BlankNode(b) => Ok(Subject::BlankNode(b.clone())),
210            SubjectPattern::Variable(_) => Err(()),
211        }
212    }
213}
214
215impl TryFrom<&PredicatePattern> for Predicate {
216    type Error = ();
217
218    fn try_from(pattern: &PredicatePattern) -> Result<Self, Self::Error> {
219        match pattern {
220            PredicatePattern::NamedNode(n) => Ok(Predicate::NamedNode(n.clone())),
221            PredicatePattern::Variable(_) => Err(()),
222        }
223    }
224}
225
226impl TryFrom<&ObjectPattern> for Object {
227    type Error = ();
228
229    fn try_from(pattern: &ObjectPattern) -> Result<Self, Self::Error> {
230        match pattern {
231            ObjectPattern::NamedNode(n) => Ok(Object::NamedNode(n.clone())),
232            ObjectPattern::BlankNode(b) => Ok(Object::BlankNode(b.clone())),
233            ObjectPattern::Literal(l) => Ok(Object::Literal(l.clone())),
234            ObjectPattern::Variable(_) => Err(()),
235        }
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_pattern_matching() {
245        let subject = NamedNode::new("http://example.org/s").unwrap();
246        let predicate = NamedNode::new("http://example.org/p").unwrap();
247        let object = Literal::new("o");
248
249        let triple = Triple::new(subject.clone(), predicate.clone(), object.clone());
250
251        // Test exact match
252        let pattern = TriplePattern::new(
253            Some(SubjectPattern::NamedNode(subject.clone())),
254            Some(PredicatePattern::NamedNode(predicate.clone())),
255            Some(ObjectPattern::Literal(object.clone())),
256        );
257        assert!(pattern.matches(&triple));
258
259        // Test wildcard match
260        let pattern = TriplePattern::new(None, None, None);
261        assert!(pattern.matches(&triple));
262
263        // Test variable match
264        let pattern = TriplePattern::new(
265            Some(SubjectPattern::Variable(Variable::new("s").unwrap())),
266            Some(PredicatePattern::Variable(Variable::new("p").unwrap())),
267            Some(ObjectPattern::Variable(Variable::new("o").unwrap())),
268        );
269        assert!(pattern.matches(&triple));
270
271        // Test non-match
272        let different_subject = NamedNode::new("http://example.org/different").unwrap();
273        let pattern = TriplePattern::new(
274            Some(SubjectPattern::NamedNode(different_subject)),
275            None,
276            None,
277        );
278        assert!(!pattern.matches(&triple));
279    }
280}