flams_math_archives/triple_store/
sparql.rs

1pub mod spargebra {
2    pub use ::spargebra::Query as QueryBuilder;
3    pub use oxigraph::sparql::*;
4    pub use spargebra::{algebra, term};
5}
6use sparesults::QueryResultsSerializer;
7pub use spargebra::*;
8
9use ftml_ontology::narrative::elements::{ParagraphOrProblemKind, problems::CognitiveDimension};
10use ftml_uris::{DocumentElementUri, FtmlUri, SymbolUri};
11use std::marker::PhantomData;
12use ulo::rdf_types::NamedNode;
13
14pub trait TermPattern {
15    fn into_term(self) -> spargebra::term::TermPattern;
16}
17impl<U: FtmlUri> TermPattern for &'_ U {
18    #[inline]
19    fn into_term(self) -> spargebra::term::TermPattern {
20        spargebra::term::TermPattern::NamedNode(self.to_iri())
21    }
22}
23impl TermPattern for NamedNode {
24    #[inline]
25    fn into_term(self) -> spargebra::term::TermPattern {
26        self.into()
27    }
28}
29
30pub trait NamedNodePattern {
31    fn into_named(self) -> spargebra::term::NamedNodePattern;
32}
33impl<U: FtmlUri> NamedNodePattern for &'_ U {
34    #[inline]
35    fn into_named(self) -> spargebra::term::NamedNodePattern {
36        spargebra::term::NamedNodePattern::NamedNode(self.to_iri())
37    }
38}
39/*
40impl<T> NamedNodePattern for T
41where
42    T: Into<spargebra::term::NamedNodePattern>,
43{
44    #[inline]
45    fn into_named(self) -> spargebra::term::NamedNodePattern {
46        self.into()
47    }
48}
49impl<T> TermPattern for T
50where
51    T: Into<spargebra::term::TermPattern>,
52{
53    #[inline]
54    fn into_term(self) -> spargebra::term::TermPattern {
55        self.into()
56    }
57}
58 */
59
60/*
61pub struct Var(pub char);
62impl From<Var> for spargebra::term::TermPattern {
63    fn from(v: Var) -> Self {
64        Self::Variable(ulo::rdf_types::Variable::new_unchecked(v.0))
65    }
66}
67impl From<Var> for spargebra::term::NamedNodePattern {
68    fn from(v: Var) -> Self {
69        Self::Variable(ulo::rdf_types::Variable::new_unchecked(v.0))
70    }
71}
72
73pub struct Select<S: TermPattern, P: NamedNodePattern, O: TermPattern> {
74    pub subject: S,
75    pub pred: P,
76    pub object: O,
77}
78impl<S: TermPattern, P: NamedNodePattern, O: TermPattern> From<Select<S, P, O>>
79    for spargebra::Query
80{
81    fn from(s: Select<S, P, O>) -> Self {
82        spargebra::QueryBuilder::Select {
83            dataset: None,
84            base_iri: None,
85            pattern: spargebra::algebra::GraphPattern::Distinct {
86                inner: Box::new(spargebra::algebra::GraphPattern::Bgp {
87                    patterns: vec![spargebra::term::TriplePattern {
88                        subject: s.subject.into_term(),
89                        predicate: s.pred.into_named(),
90                        object: s.object.into_term(),
91                    }],
92                }),
93            },
94        }
95        .into()
96    }
97}
98 */
99
100#[derive(Debug, thiserror::Error)]
101pub enum QueryError {
102    #[error("{0}")]
103    Syntax(#[from] spargebra::SparqlSyntaxError),
104    #[error("{0}")]
105    Evaluation(#[from] QueryEvaluationError),
106    #[error("{0}")]
107    Utf8(#[from] std::string::FromUtf8Error),
108}
109
110pub struct QueryResult<'r>(pub(super) QueryResults<'r>);
111impl<'r> AsRef<QueryResults<'r>> for QueryResult<'r> {
112    #[inline]
113    fn as_ref(&self) -> &QueryResults<'r> {
114        &self.0
115    }
116}
117impl<'r> std::ops::Deref for QueryResult<'r> {
118    type Target = QueryResults<'r>;
119    #[inline]
120    fn deref(&self) -> &Self::Target {
121        &self.0
122    }
123}
124impl<'r> QueryResult<'r> {
125    /// ### Errors
126    pub fn into_json(self) -> Result<String, std::io::Error> {
127        use sparesults::QueryResultsFormat;
128        let mut buf = Vec::new();
129        let ser = QueryResultsSerializer::from_format(QueryResultsFormat::Json);
130        match self.0 {
131            QueryResults::Boolean(b) => {
132                ser.serialize_boolean_to_writer(&mut buf, b)?;
133            }
134            QueryResults::Solutions(sol) => {
135                let mut ser =
136                    ser.serialize_solutions_to_writer(&mut buf, sol.variables().to_vec())?;
137                for s in sol.flatten() {
138                    ser.serialize(s.iter())?;
139                }
140                ser.finish()?;
141            }
142            QueryResults::Graph(_) => {
143                return Ok(String::new());
144                //self.0.write_graph(&mut buf, oxigraph::io::RdfFormat::Turtle)?;
145            }
146        }
147        String::from_utf8(buf).map_err(|e| std::io::Error::other(e.to_string()))
148    }
149
150    #[must_use]
151    pub fn into_uris<U: FtmlUri>(self) -> RetIter<'r, U> {
152        RetIter(
153            match self.0 {
154                QueryResults::Boolean(_) | QueryResults::Graph(_) => RetIterI::None,
155                QueryResults::Solutions(sols) => RetIterI::Sols(sols),
156            },
157            PhantomData,
158        )
159    }
160}
161
162#[derive(Default)]
163enum RetIterI<'r> {
164    #[default]
165    None,
166    Sols(QuerySolutionIter<'r>),
167}
168
169pub struct RetIter<'r, U: FtmlUri>(RetIterI<'r>, PhantomData<U>);
170impl<U: FtmlUri> Default for RetIter<'_, U> {
171    #[inline]
172    fn default() -> Self {
173        Self(RetIterI::default(), PhantomData)
174    }
175}
176
177impl<U: FtmlUri> Iterator for RetIter<'_, U> {
178    type Item = U;
179    fn next(&mut self) -> Option<Self::Item> {
180        let RetIterI::Sols(s) = &mut self.0 else {
181            return None;
182        };
183        loop {
184            let s = match s.next() {
185                None => return None,
186                Some(Err(_)) => continue,
187                Some(Ok(s)) => s,
188            };
189            let [Some(spargebra::term::Term::NamedNode(n))] = s.values() else {
190                continue;
191            };
192            if let Ok(u) = U::from_iri(n.as_ref()) {
193                return Some(u);
194            }
195        }
196    }
197}
198
199pub struct LOIter<'r> {
200    pub(super) inner: QuerySolutionIter<'r>,
201}
202impl Iterator for LOIter<'_> {
203    type Item = (DocumentElementUri, ParagraphOrProblemKind);
204    fn next(&mut self) -> Option<Self::Item> {
205        use spargebra::term::Term;
206        loop {
207            let s = match self.inner.next() {
208                None => return None,
209                Some(Err(_)) => continue,
210                Some(Ok(s)) => s,
211            };
212            let Some(Term::NamedNode(n)) = s.get("x") else {
213                continue;
214            };
215            let Ok(uri) = DocumentElementUri::from_iri(n.as_ref()) else {
216                continue;
217            };
218            let n = match s.get("R") {
219                Some(Term::Literal(l)) if l.value() == "DEF" => {
220                    return Some((uri, ParagraphOrProblemKind::Definition));
221                }
222                Some(Term::Literal(l)) if l.value() == "EX" => {
223                    return Some((uri, ParagraphOrProblemKind::Example));
224                }
225                Some(Term::NamedNode(s)) => s,
226                _ => continue,
227            };
228            let Some(cd) = CognitiveDimension::from_iri(n.as_ref()) else {
229                continue;
230            };
231            let sub =
232                matches!(s.get("t"),Some(Term::NamedNode(n)) if n.as_ref() == ulo::ulo::subproblem);
233            return Some((
234                uri,
235                if sub {
236                    ParagraphOrProblemKind::SubProblem(cd)
237                } else {
238                    ParagraphOrProblemKind::Problem(cd)
239                },
240            ));
241        }
242    }
243}
244
245#[must_use]
246pub fn lo_query(s: &SymbolUri, problems: bool) -> ::spargebra::Query {
247    if problems {
248        crate::sparql!(SELECT DISTINCT ?x ?R ?t WHERE {
249            {
250                ?x ulo:defines s.
251                BIND("DEF" as ?R)
252            } UNION {
253                ?x ulo:example_for s.
254                BIND("EX" as ?R)
255            } UNION {
256                ?x ulo:has_objective ?b.
257                ?b ulo:po_has_symbol s.
258                ?b ulo:has_cognitive_dimension ?R.
259                ?x rdf:TYPE ?t.
260            }
261        })
262    } else {
263        crate::sparql!(SELECT DISTINCT ?x ?R WHERE {
264            {
265                ?x ulo:defines s.
266                BIND("DEF" as ?R)
267            } UNION {
268                ?x ulo:example_for s.
269                BIND("EX" as ?R)
270            }
271        })
272    }
273}
274
275#[macro_export]
276macro_rules! sparql {
277   (SELECT DISTINCT $(?$c:ident)+ WHERE {$($rest:tt)*}) => {
278       $crate::triple_store::sparql::QueryBuilder::Select {
279           dataset: None,
280           base_iri: None,
281           pattern: $crate::triple_store::sparql::algebra::GraphPattern::Distinct {
282               inner: Box::new(
283                   $crate::triple_store::sparql::algebra::GraphPattern::Project{
284                       inner: Box::new($crate::sparql!(@PAT $($rest)*)),
285                       variables: vec![$(
286                           $crate::triple_store::sparql::term::Variable::new_unchecked(stringify!($c)).into()
287                       ),*]
288                   }
289               )
290           }
291       }
292
293   };
294   (SELECT $(?$c:ident)* WHERE {$($rest:tt)*}) => {
295       $crate::triple_store::sparql::QueryBuilder::Select {
296           dataset: None,
297           base_iri: None,
298           pattern:
299           $crate::triple_store::sparql::algebra::GraphPattern::Project{
300               inner: Box::new($crate::sparql!(@PAT $($rest)*)),
301               variables: vec![$(
302                   $crate::triple_store::sparql::term::Variable::new_unchecked(stringify!($c)).into()
303               ),*]
304           }
305       }
306   };
307   (@PAT {$($first:tt)*} UNION $($rest:tt)*) => {
308       $crate::triple_store::sparql::algebra::GraphPattern::Union {
309           left:Box::new($crate::sparql!(@TRIP {} {} $($first)*).into()),
310           right:Box::new($crate::sparql!(@PAT $($rest)*)),
311       }
312   };
313   (@PAT {$($rest:tt)*}) => {
314       $crate::sparql!(@TRIP {} {} $($rest)*)
315   };
316   (@PAT $($rest:tt)*) => {
317       $crate::sparql!(@TRIP {} {} $($rest)*)
318   };
319   (@TRIP {$($trips:tt)*} {$($binds:tt)*} ?$v:ident $path:ident:$pred:ident ?$v2:ident . $($rest:tt)*) => {
320       $crate::sparql!(@TRIP {
321           $($trips)*
322           (
323               $crate::triple_store::sparql::term::TriplePattern {
324                   subject: $crate::triple_store::sparql::term::Variable::new_unchecked(stringify!($v)).into(),
325                   predicate: ::ulo::$path::$pred.into_owned().into(),
326                   object: $crate::triple_store::sparql::term::Variable::new_unchecked(stringify!($v2)).into(),
327               }
328           )
329       } { $($binds)* } $($rest)* )
330   };
331   (@TRIP {$($trips:tt)*} {$($binds:tt)*} ?$v:ident $path:ident:$pred:ident $p2:ident:$t:ident . $($rest:tt)*) => {
332       $crate::sparql!(@TRIP {
333           $($trips)*
334           (
335               $crate::triple_store::sparql::term::TriplePattern {
336                   subject: $crate::triple_store::sparql::term::Variable::new_unchecked(stringify!($v)).into(),
337                   predicate: ::ulo::$path::$pred.into_owned().into(),
338                   object: ::ulo::$p2::$t.into_owned().into(),
339               }
340           )
341       } { $($binds)* } $($rest)* )
342   };
343   (@TRIP {$($trips:tt)*} {$($binds:tt)*} $node:ident $path:ident:$pred:ident ?$v2:ident . $($rest:tt)*) => {
344       $crate::sparql!(@TRIP {
345           $($trips)*
346           (
347               $crate::triple_store::sparql::term::TriplePattern {
348                   subject: $crate::triple_store::sparql::TermPattern::into_term($node).into(),
349                   predicate: ::ulo::$path::$pred.into_owned().into(),
350                   object: $crate::triple_store::sparql::term::Variable::new_unchecked(stringify!($v2)).into(),
351               }
352           )
353       } { $($binds)* } $($rest)* )
354   };
355   (@TRIP {$($trips:tt)*} {$($binds:tt)*} ?$v:ident $path:ident:$pred:ident $node:ident . $($rest:tt)*) => {
356       $crate::sparql!(@TRIP {
357           $($trips)*
358           (
359               $crate::triple_store::sparql::term::TriplePattern {
360                   subject: $crate::triple_store::sparql::term::Variable::new_unchecked(stringify!($v)).into(),
361                   predicate: ::ulo::$path::$pred.into_owned().into(),
362                   object:$crate::triple_store::sparql::TermPattern::into_term($node),
363               }
364           )
365       } { $($binds)* } $($rest)* )
366   };
367   (@TRIP {$($trips:tt)*} {$($binds:tt)*} BIND($name:literal as ?$v:ident) $($rest:tt)*) => {
368       $crate::sparql!(@TRIP { $($trips)* } { $($binds)* ($name,$v) } $($rest)* )
369   };
370   (@TRIP {} {}) => {
371       compile_error!("pattern has no body")
372   };
373   (@TRIP { $(($e:expr))+ } {}) => {
374       $crate::triple_store::sparql::algebra::GraphPattern::Bgp {
375           patterns:vec![ $($e),* ]
376       }
377   };
378   (@TRIP {$( ($e:expr) )+} {($name:literal,$v:ident) $($rest:tt)*}) => {
379       $crate::triple_store::sparql::algebra::GraphPattern::Extend {
380           inner:Box::new(
381               $crate::sparql!(@TRIP { $(($e))+ } { $($rest)* })
382           ),
383           variable: $crate::triple_store::sparql::term::Variable::new_unchecked(stringify!($v)).into(),
384           expression: $crate::triple_store::sparql::algebra::Expression::Literal($name.into())
385       }
386   };
387}