strixonomy_query/
sparql.rs1use crate::sparql_update::is_sparql_update;
2use crate::QueryError;
3use oxigraph::sparql::{QueryResults, QuerySolution};
4use serde::Serialize;
5use std::collections::BTreeMap;
6use strixonomy_catalog::OntologyCatalog;
7use strixonomy_core::limits::{MAX_QUERY_BYTES, MAX_SPARQL_RESULT_ROWS};
8
9pub type Result<T> = std::result::Result<T, QueryError>;
10
11#[derive(Debug, Clone, Serialize)]
12pub struct SparqlResult {
13 pub columns: Vec<String>,
14 pub rows: Vec<BTreeMap<String, String>>,
15 pub truncated: bool,
16}
17
18pub fn run_sparql(catalog: &OntologyCatalog, sparql: &str) -> Result<SparqlResult> {
19 if sparql.len() > MAX_QUERY_BYTES {
20 return Err(QueryError::Sparql(format!(
21 "query exceeds maximum length of {MAX_QUERY_BYTES} bytes"
22 )));
23 }
24
25 if is_sparql_update(sparql) {
26 return Err(QueryError::Sparql(
27 "SPARQL update operations are not supported; use read-only queries".to_string(),
28 ));
29 }
30
31 let results = catalog.store().query(sparql).map_err(|e| QueryError::Sparql(e.to_string()))?;
32
33 match results {
34 QueryResults::Solutions(solutions) => {
35 let columns: Vec<String> =
36 solutions.variables().iter().map(|v| v.as_str().to_string()).collect();
37 let mut rows = Vec::new();
38 let mut truncated = false;
39 for solution in solutions {
40 if rows.len() >= MAX_SPARQL_RESULT_ROWS {
41 truncated = true;
42 break;
43 }
44 let solution = solution.map_err(|e| QueryError::Sparql(e.to_string()))?;
45 rows.push(solution_to_row(&solution, &columns));
46 }
47 Ok(SparqlResult { columns, rows, truncated })
48 }
49 QueryResults::Boolean(value) => {
50 let mut row = BTreeMap::new();
51 row.insert("boolean".into(), value.to_string());
52 Ok(SparqlResult { columns: vec!["boolean".into()], rows: vec![row], truncated: false })
53 }
54 QueryResults::Graph(_) => Err(QueryError::Sparql(
55 "CONSTRUCT/DESCRIBE graph results are not supported in v0.1".to_string(),
56 )),
57 }
58}
59
60fn solution_to_row(solution: &QuerySolution, columns: &[String]) -> BTreeMap<String, String> {
61 let mut row = BTreeMap::new();
62 for col in columns {
63 let value = solution.get(col.as_str()).map(|term| term.to_string()).unwrap_or_default();
64 row.insert(col.clone(), value);
65 }
66 row
67}
68
69pub fn to_json(result: &SparqlResult) -> Result<String> {
70 serde_json::to_string_pretty(result).map_err(|e| QueryError::Export(e.to_string()))
71}