Skip to main content

strixonomy_query/
sparql.rs

1use 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}