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    /// RDF-star: a quoted triple used as a subject
80    QuotedTriple(Box<crate::query::algebra::AlgebraTriplePattern>),
81}
82
83impl SubjectPattern {
84    /// Get the string representation
85    pub fn as_str(&self) -> &str {
86        match self {
87            SubjectPattern::NamedNode(nn) => nn.as_str(),
88            SubjectPattern::BlankNode(bn) => bn.as_str(),
89            SubjectPattern::Variable(v) => v.as_str(),
90            SubjectPattern::QuotedTriple(_) => "<<quoted-triple>>",
91        }
92    }
93
94    fn matches(&self, subject: &Subject) -> bool {
95        match (self, subject) {
96            (SubjectPattern::NamedNode(pn), Subject::NamedNode(sn)) => pn == sn,
97            (SubjectPattern::BlankNode(pb), Subject::BlankNode(sb)) => pb == sb,
98            (SubjectPattern::Variable(_), _) => true,
99            // A quoted-triple pattern matches any quoted-triple subject (variable binding
100            // refinement is handled by the query evaluator, not at the pattern level).
101            (SubjectPattern::QuotedTriple(_), Subject::QuotedTriple(_)) => true,
102            _ => false,
103        }
104    }
105}
106
107/// Pattern for matching predicates
108#[derive(Debug, Clone, PartialEq, Eq, Hash)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110pub enum PredicatePattern {
111    NamedNode(NamedNode),
112    Variable(Variable),
113}
114
115impl PredicatePattern {
116    /// Get the string representation
117    pub fn as_str(&self) -> &str {
118        match self {
119            PredicatePattern::NamedNode(nn) => nn.as_str(),
120            PredicatePattern::Variable(v) => v.as_str(),
121        }
122    }
123
124    fn matches(&self, predicate: &Predicate) -> bool {
125        match (self, predicate) {
126            (PredicatePattern::NamedNode(pn), Predicate::NamedNode(sn)) => pn == sn,
127            (PredicatePattern::Variable(_), _) => true,
128            _ => false,
129        }
130    }
131}
132
133/// Pattern for matching objects
134#[derive(Debug, Clone, PartialEq, Eq, Hash)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136pub enum ObjectPattern {
137    NamedNode(NamedNode),
138    BlankNode(BlankNode),
139    Literal(Literal),
140    Variable(Variable),
141    /// RDF-star: a quoted triple used as an object
142    QuotedTriple(Box<crate::query::algebra::AlgebraTriplePattern>),
143}
144
145impl ObjectPattern {
146    /// Get the string representation
147    pub fn as_str(&self) -> &str {
148        match self {
149            ObjectPattern::NamedNode(nn) => nn.as_str(),
150            ObjectPattern::BlankNode(bn) => bn.as_str(),
151            ObjectPattern::Literal(l) => l.value(),
152            ObjectPattern::Variable(v) => v.as_str(),
153            ObjectPattern::QuotedTriple(_) => "<<quoted-triple>>",
154        }
155    }
156
157    fn matches(&self, object: &Object) -> bool {
158        match (self, object) {
159            (ObjectPattern::NamedNode(pn), Object::NamedNode(on)) => pn == on,
160            (ObjectPattern::BlankNode(pb), Object::BlankNode(ob)) => pb == ob,
161            (ObjectPattern::Literal(pl), Object::Literal(ol)) => pl == ol,
162            (ObjectPattern::Variable(_), _) => true,
163            // A quoted-triple pattern matches any quoted-triple object.
164            (ObjectPattern::QuotedTriple(_), Object::QuotedTriple(_)) => true,
165            _ => false,
166        }
167    }
168}
169
170// TryFrom implementations for converting TermPattern to positional patterns.
171// Using TryFrom rather than From because certain TermPattern variants are semantically
172// invalid for particular positions (e.g., Literal cannot be a subject in RDF).
173use crate::query::algebra::TermPattern;
174
175/// Error type for invalid TermPattern-to-positional-pattern conversions.
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct InvalidPatternConversion {
178    pub reason: &'static str,
179}
180
181impl std::fmt::Display for InvalidPatternConversion {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        write!(f, "Invalid pattern conversion: {}", self.reason)
184    }
185}
186
187impl std::error::Error for InvalidPatternConversion {}
188
189impl TryFrom<TermPattern> for SubjectPattern {
190    type Error = InvalidPatternConversion;
191
192    fn try_from(term: TermPattern) -> Result<Self, Self::Error> {
193        match term {
194            TermPattern::NamedNode(n) => Ok(SubjectPattern::NamedNode(n)),
195            TermPattern::BlankNode(b) => Ok(SubjectPattern::BlankNode(b)),
196            TermPattern::Variable(v) => Ok(SubjectPattern::Variable(v)),
197            TermPattern::QuotedTriple(qt) => Ok(SubjectPattern::QuotedTriple(qt)),
198            TermPattern::Literal(_) => Err(InvalidPatternConversion {
199                reason: "Literals cannot be used as subjects in RDF",
200            }),
201        }
202    }
203}
204
205impl TryFrom<TermPattern> for PredicatePattern {
206    type Error = InvalidPatternConversion;
207
208    fn try_from(term: TermPattern) -> Result<Self, Self::Error> {
209        match term {
210            TermPattern::NamedNode(n) => Ok(PredicatePattern::NamedNode(n)),
211            TermPattern::Variable(v) => Ok(PredicatePattern::Variable(v)),
212            TermPattern::BlankNode(_) => Err(InvalidPatternConversion {
213                reason: "Blank nodes cannot be used as predicates in RDF",
214            }),
215            TermPattern::Literal(_) => Err(InvalidPatternConversion {
216                reason: "Literals cannot be used as predicates in RDF",
217            }),
218            TermPattern::QuotedTriple(_) => Err(InvalidPatternConversion {
219                reason: "Quoted triples cannot be used as predicates in RDF",
220            }),
221        }
222    }
223}
224
225impl TryFrom<TermPattern> for ObjectPattern {
226    type Error = InvalidPatternConversion;
227
228    fn try_from(term: TermPattern) -> Result<Self, Self::Error> {
229        match term {
230            TermPattern::NamedNode(n) => Ok(ObjectPattern::NamedNode(n)),
231            TermPattern::BlankNode(b) => Ok(ObjectPattern::BlankNode(b)),
232            TermPattern::Literal(l) => Ok(ObjectPattern::Literal(l)),
233            TermPattern::Variable(v) => Ok(ObjectPattern::Variable(v)),
234            TermPattern::QuotedTriple(qt) => Ok(ObjectPattern::QuotedTriple(qt)),
235        }
236    }
237}
238
239// TryFrom implementations for converting patterns to concrete terms
240impl TryFrom<&SubjectPattern> for Subject {
241    type Error = ();
242
243    fn try_from(pattern: &SubjectPattern) -> Result<Self, Self::Error> {
244        match pattern {
245            SubjectPattern::NamedNode(n) => Ok(Subject::NamedNode(n.clone())),
246            SubjectPattern::BlankNode(b) => Ok(Subject::BlankNode(b.clone())),
247            SubjectPattern::Variable(_) => Err(()),
248            SubjectPattern::QuotedTriple(qt) => {
249                // Convert AlgebraTriplePattern to a concrete QuotedTriple if possible.
250                // This requires all sub-terms to be ground (non-variable).
251                use crate::model::star::QuotedTriple;
252                let inner_subj = match &qt.subject {
253                    TermPattern::NamedNode(n) => Subject::NamedNode(n.clone()),
254                    TermPattern::BlankNode(b) => Subject::BlankNode(b.clone()),
255                    _ => return Err(()),
256                };
257                let inner_pred = match &qt.predicate {
258                    TermPattern::NamedNode(n) => Predicate::NamedNode(n.clone()),
259                    _ => return Err(()),
260                };
261                let inner_obj = match &qt.object {
262                    TermPattern::NamedNode(n) => Object::NamedNode(n.clone()),
263                    TermPattern::BlankNode(b) => Object::BlankNode(b.clone()),
264                    TermPattern::Literal(l) => Object::Literal(l.clone()),
265                    _ => return Err(()),
266                };
267                Ok(Subject::QuotedTriple(Box::new(QuotedTriple::new(
268                    Triple::new(inner_subj, inner_pred, inner_obj),
269                ))))
270            }
271        }
272    }
273}
274
275impl TryFrom<&PredicatePattern> for Predicate {
276    type Error = ();
277
278    fn try_from(pattern: &PredicatePattern) -> Result<Self, Self::Error> {
279        match pattern {
280            PredicatePattern::NamedNode(n) => Ok(Predicate::NamedNode(n.clone())),
281            PredicatePattern::Variable(_) => Err(()),
282        }
283    }
284}
285
286impl TryFrom<&ObjectPattern> for Object {
287    type Error = ();
288
289    fn try_from(pattern: &ObjectPattern) -> Result<Self, Self::Error> {
290        match pattern {
291            ObjectPattern::NamedNode(n) => Ok(Object::NamedNode(n.clone())),
292            ObjectPattern::BlankNode(b) => Ok(Object::BlankNode(b.clone())),
293            ObjectPattern::Literal(l) => Ok(Object::Literal(l.clone())),
294            ObjectPattern::Variable(_) => Err(()),
295            ObjectPattern::QuotedTriple(qt) => {
296                // Convert AlgebraTriplePattern to a concrete QuotedTriple if all terms are ground.
297                use crate::model::star::QuotedTriple;
298                let inner_subj = match &qt.subject {
299                    TermPattern::NamedNode(n) => Subject::NamedNode(n.clone()),
300                    TermPattern::BlankNode(b) => Subject::BlankNode(b.clone()),
301                    _ => return Err(()),
302                };
303                let inner_pred = match &qt.predicate {
304                    TermPattern::NamedNode(n) => Predicate::NamedNode(n.clone()),
305                    _ => return Err(()),
306                };
307                let inner_obj = match &qt.object {
308                    TermPattern::NamedNode(n) => Object::NamedNode(n.clone()),
309                    TermPattern::BlankNode(b) => Object::BlankNode(b.clone()),
310                    TermPattern::Literal(l) => Object::Literal(l.clone()),
311                    _ => return Err(()),
312                };
313                Ok(Object::QuotedTriple(Box::new(QuotedTriple::new(
314                    Triple::new(inner_subj, inner_pred, inner_obj),
315                ))))
316            }
317        }
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    #[test]
326    fn test_pattern_matching() {
327        let subject = NamedNode::new("http://example.org/s").expect("valid IRI");
328        let predicate = NamedNode::new("http://example.org/p").expect("valid IRI");
329        let object = Literal::new("o");
330
331        let triple = Triple::new(subject.clone(), predicate.clone(), object.clone());
332
333        // Test exact match
334        let pattern = TriplePattern::new(
335            Some(SubjectPattern::NamedNode(subject.clone())),
336            Some(PredicatePattern::NamedNode(predicate.clone())),
337            Some(ObjectPattern::Literal(object.clone())),
338        );
339        assert!(pattern.matches(&triple));
340
341        // Test wildcard match
342        let pattern = TriplePattern::new(None, None, None);
343        assert!(pattern.matches(&triple));
344
345        // Test variable match
346        let pattern = TriplePattern::new(
347            Some(SubjectPattern::Variable(
348                Variable::new("s").expect("valid variable name"),
349            )),
350            Some(PredicatePattern::Variable(
351                Variable::new("p").expect("valid variable name"),
352            )),
353            Some(ObjectPattern::Variable(
354                Variable::new("o").expect("valid variable name"),
355            )),
356        );
357        assert!(pattern.matches(&triple));
358
359        // Test non-match
360        let different_subject = NamedNode::new("http://example.org/different").expect("valid IRI");
361        let pattern = TriplePattern::new(
362            Some(SubjectPattern::NamedNode(different_subject)),
363            None,
364            None,
365        );
366        assert!(!pattern.matches(&triple));
367    }
368}