Skip to main content

oxirs_core/model/
star.rs

1//! RDF-star (RDF*) support for statement annotations
2//!
3//! This module implements RDF-star extensions for SPARQL 1.2 compliance,
4//! allowing triples to be used as subjects or objects in other triples.
5
6use crate::model::{
7    NamedNode, Object, ObjectTerm, Predicate, RdfTerm, Subject, SubjectTerm, Triple,
8};
9use crate::query::algebra::{AlgebraTriplePattern, TermPattern};
10use crate::OxirsError;
11use std::fmt;
12use std::sync::Arc;
13
14/// A quoted triple that can be used as a subject or object in RDF-star
15#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct QuotedTriple {
17    /// The inner triple being quoted
18    inner: Arc<Triple>,
19}
20
21impl QuotedTriple {
22    /// Create a new quoted triple
23    pub fn new(triple: Triple) -> Self {
24        QuotedTriple {
25            inner: Arc::new(triple),
26        }
27    }
28
29    /// Create from an existing `Arc<Triple>`
30    pub fn from_arc(triple: Arc<Triple>) -> Self {
31        QuotedTriple { inner: triple }
32    }
33
34    /// Get the inner triple
35    pub fn inner(&self) -> &Triple {
36        &self.inner
37    }
38
39    /// Get the subject of the quoted triple
40    pub fn subject(&self) -> &Subject {
41        self.inner.subject()
42    }
43
44    /// Get the predicate of the quoted triple
45    pub fn predicate(&self) -> &Predicate {
46        self.inner.predicate()
47    }
48
49    /// Get the object of the quoted triple
50    pub fn object(&self) -> &Object {
51        self.inner.object()
52    }
53
54    /// Convert to a triple reference
55    pub fn as_ref(&self) -> QuotedTripleRef<'_> {
56        QuotedTripleRef { inner: &self.inner }
57    }
58}
59
60impl fmt::Display for QuotedTriple {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(f, "<< {} >>", self.inner)
63    }
64}
65
66impl RdfTerm for QuotedTriple {
67    fn as_str(&self) -> &str {
68        // For quoted triples, we return a synthetic string representation
69        "<<quoted-triple>>"
70    }
71
72    fn is_quoted_triple(&self) -> bool {
73        true
74    }
75}
76
77impl SubjectTerm for QuotedTriple {}
78impl ObjectTerm for QuotedTriple {}
79
80// Custom serialization for QuotedTriple to handle Arc<Triple>
81#[cfg(feature = "serde")]
82impl serde::Serialize for QuotedTriple {
83    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84    where
85        S: serde::Serializer,
86    {
87        // Serialize the inner triple directly
88        self.inner.as_ref().serialize(serializer)
89    }
90}
91
92#[cfg(feature = "serde")]
93impl<'de> serde::Deserialize<'de> for QuotedTriple {
94    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
95    where
96        D: serde::Deserializer<'de>,
97    {
98        let triple = Triple::deserialize(deserializer)?;
99        Ok(QuotedTriple::new(triple))
100    }
101}
102
103/// A borrowed quoted triple reference
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
105pub struct QuotedTripleRef<'a> {
106    inner: &'a Triple,
107}
108
109impl<'a> QuotedTripleRef<'a> {
110    /// Create a new quoted triple reference
111    pub fn new(triple: &'a Triple) -> Self {
112        QuotedTripleRef { inner: triple }
113    }
114
115    /// Get the inner triple
116    pub fn inner(&self) -> &'a Triple {
117        self.inner
118    }
119
120    /// Convert to owned quoted triple
121    pub fn to_owned(&self) -> QuotedTriple {
122        QuotedTriple::new(self.inner.clone())
123    }
124}
125
126impl<'a> fmt::Display for QuotedTripleRef<'a> {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        write!(f, "<< {} >>", self.inner)
129    }
130}
131
132impl<'a> RdfTerm for QuotedTripleRef<'a> {
133    fn as_str(&self) -> &str {
134        "<<quoted-triple>>"
135    }
136
137    fn is_quoted_triple(&self) -> bool {
138        true
139    }
140}
141
142/// RDF-star annotation syntax support
143pub struct Annotation {
144    /// The statement being annotated (as a quoted triple)
145    pub statement: QuotedTriple,
146    /// The annotation property
147    pub property: NamedNode,
148    /// The annotation value
149    pub value: Object,
150}
151
152impl Annotation {
153    /// Create a new annotation
154    pub fn new(statement: Triple, property: NamedNode, value: Object) -> Self {
155        Annotation {
156            statement: QuotedTriple::new(statement),
157            property,
158            value,
159        }
160    }
161
162    /// Convert annotation to a regular triple with quoted triple as subject
163    pub fn to_triple(&self) -> Triple {
164        Triple::new(
165            Subject::QuotedTriple(Box::new(self.statement.clone())),
166            self.property.clone(),
167            self.value.clone(),
168        )
169    }
170}
171
172/// RDF-star pattern for SPARQL queries
173#[derive(Debug, Clone, PartialEq, Eq, Hash)]
174pub enum StarPattern {
175    /// A regular triple pattern
176    Triple(AlgebraTriplePattern),
177    /// A quoted triple pattern
178    QuotedTriple {
179        /// Subject pattern (can be nested quoted triple)
180        subject: Box<StarPattern>,
181        /// Predicate pattern
182        predicate: TermPattern,
183        /// Object pattern (can be nested quoted triple)
184        object: Box<StarPattern>,
185    },
186    /// An annotation pattern
187    Annotation {
188        /// The annotated statement pattern
189        statement: Box<StarPattern>,
190        /// Annotation property pattern
191        property: TermPattern,
192        /// Annotation value pattern
193        value: TermPattern,
194    },
195}
196
197impl StarPattern {
198    /// Check if pattern contains variables
199    pub fn has_variables(&self) -> bool {
200        match self {
201            StarPattern::Triple(pattern) => {
202                matches!(pattern.subject, TermPattern::Variable(_))
203                    || matches!(pattern.predicate, TermPattern::Variable(_))
204                    || matches!(pattern.object, TermPattern::Variable(_))
205            }
206            StarPattern::QuotedTriple {
207                subject,
208                predicate: _,
209                object,
210            } => subject.has_variables() || object.has_variables(),
211            StarPattern::Annotation {
212                statement,
213                property: _,
214                value: _,
215            } => statement.has_variables(),
216        }
217    }
218
219    /// Get all variables in the pattern
220    pub fn variables(&self) -> Vec<crate::model::Variable> {
221        let mut vars = Vec::new();
222        self.collect_variables(&mut vars);
223        vars
224    }
225
226    fn collect_variables(&self, _vars: &mut Vec<crate::model::Variable>) {
227        match self {
228            StarPattern::Triple(pattern) => {
229                if let TermPattern::Variable(ref v) = pattern.subject {
230                    _vars.push(v.clone());
231                }
232                if let TermPattern::Variable(ref v) = pattern.predicate {
233                    _vars.push(v.clone());
234                }
235                if let TermPattern::Variable(ref v) = pattern.object {
236                    _vars.push(v.clone());
237                }
238            }
239            StarPattern::QuotedTriple {
240                subject,
241                predicate: _,
242                object,
243            } => {
244                subject.collect_variables(_vars);
245                object.collect_variables(_vars);
246            }
247            StarPattern::Annotation {
248                statement,
249                property: _,
250                value: _,
251            } => {
252                statement.collect_variables(_vars);
253            }
254        }
255    }
256}
257
258/// RDF-star serialization format extensions
259pub mod serialization {
260    use super::*;
261
262    /// Turtle-star syntax extensions
263    pub mod turtle_star {
264        use super::*;
265
266        /// Serialize a quoted triple in Turtle-star syntax
267        pub fn serialize_quoted_triple(qt: &QuotedTriple) -> String {
268            format!("<< {} {} {} >>", qt.subject(), qt.predicate(), qt.object())
269        }
270
271        /// Parse a quoted triple from Turtle-star syntax
272        pub fn parse_quoted_triple(input: &str) -> Result<QuotedTriple, OxirsError> {
273            // Simplified parser - in production would use proper tokenization
274            let trimmed = input.trim();
275            if !trimmed.starts_with("<<") || !trimmed.ends_with(">>") {
276                return Err(OxirsError::Parse(
277                    "Invalid quoted triple syntax".to_string(),
278                ));
279            }
280
281            // Extract inner content
282            let _inner = &trimmed[2..trimmed.len() - 2].trim();
283
284            // Parse inner triple (simplified)
285            // In production, would integrate with full Turtle parser
286            Err(OxirsError::Parse(
287                "Quoted triple parsing not yet implemented".to_string(),
288            ))
289        }
290    }
291
292    /// SPARQL-star syntax extensions
293    pub mod sparql_star {
294        use super::*;
295
296        /// Format a star pattern for SPARQL
297        pub fn format_star_pattern(pattern: &StarPattern) -> String {
298            match pattern {
299                StarPattern::Triple(pattern) => pattern.to_string(),
300                StarPattern::QuotedTriple {
301                    subject,
302                    predicate: _,
303                    object,
304                } => {
305                    format!(
306                        "<< {} {} {} >>",
307                        format_star_pattern(subject),
308                        "PREDICATE",
309                        format_star_pattern(object)
310                    )
311                }
312                StarPattern::Annotation {
313                    statement,
314                    property: _,
315                    value: _,
316                } => {
317                    format!(
318                        "{} {} {}",
319                        format_star_pattern(statement),
320                        "PROPERTY",
321                        "VALUE"
322                    )
323                }
324            }
325        }
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332    use crate::model::{Literal, NamedNode};
333
334    #[test]
335    fn test_quoted_triple() {
336        let subject = NamedNode::new("http://example.org/alice").unwrap();
337        let predicate = NamedNode::new("http://example.org/says").unwrap();
338        let object = Object::Literal(Literal::new("Hello"));
339
340        let triple = Triple::new(subject, predicate, object);
341        let quoted = QuotedTriple::new(triple.clone());
342
343        assert_eq!(quoted.inner(), &triple);
344        assert_eq!(
345            format!("{quoted}"),
346            "<< <http://example.org/alice> <http://example.org/says> \"Hello\" . >>"
347        );
348    }
349
350    #[test]
351    fn test_annotation() {
352        let subject = NamedNode::new("http://example.org/alice").unwrap();
353        let predicate = NamedNode::new("http://example.org/age").unwrap();
354        let object = Object::Literal(Literal::new_typed(
355            "30",
356            NamedNode::new("http://www.w3.org/2001/XMLSchema#integer").unwrap(),
357        ));
358
359        let statement = Triple::new(subject, predicate, object);
360        let ann_property = NamedNode::new("http://example.org/confidence").unwrap();
361        let ann_value = Object::Literal(Literal::new_typed(
362            "0.9",
363            NamedNode::new("http://www.w3.org/2001/XMLSchema#double").unwrap(),
364        ));
365
366        let annotation = Annotation::new(statement, ann_property, ann_value);
367        let ann_triple = annotation.to_triple();
368
369        assert!(matches!(ann_triple.subject(), Subject::QuotedTriple(_)));
370    }
371}