Skip to main content

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