flams_math_archives/triple_store/
sparql.rs1pub 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}