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 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#[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 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 }
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}