diff --git a/lib/chrontext/src/combiner.rs b/lib/chrontext/src/combiner.rs index 6813545..d9f4b5f 100644 --- a/lib/chrontext/src/combiner.rs +++ b/lib/chrontext/src/combiner.rs @@ -8,25 +8,24 @@ pub(crate) mod virtualized_queries; use representation::query_context::Context; use crate::preparing::TimeseriesQueryPrepper; -use crate::sparql_database::SparqlQueryable; +use crate::sparql_database::{SparqlQueryError, SparqlQueryable}; use query_processing::errors::QueryProcessingError; use representation::solution_mapping::SolutionMappings; use spargebra::algebra::Expression; use spargebra::Query; use std::collections::{HashMap, HashSet}; -use std::error::Error; use std::fmt::{Display, Formatter}; use std::sync::Arc; use thiserror::Error; -use virtualization::errors::VirtualizedDatabaseError; +use virtualization::errors::ChrontextError; use virtualization::{Virtualization, VirtualizedDatabase}; use virtualized_query::pushdown_setting::PushdownSetting; use virtualized_query::{BasicVirtualizedQuery, VirtualizedResultValidationError}; #[derive(Debug, Error)] pub enum CombinerError { - VirtualizedDatabaseError(VirtualizedDatabaseError), - StaticQueryExecutionError(Box), + VirtualizedDatabaseError(ChrontextError), + StaticQueryExecutionError(SparqlQueryError), QueryProcessingError(#[from] QueryProcessingError), InconsistentDatatype(String, String, String), TimeseriesValidationError(VirtualizedResultValidationError), diff --git a/lib/chrontext/src/engine.rs b/lib/chrontext/src/engine.rs index 1360d9a..7b41240 100644 --- a/lib/chrontext/src/engine.rs +++ b/lib/chrontext/src/engine.rs @@ -13,7 +13,6 @@ use representation::query_context::Context; use representation::solution_mapping::SolutionMappings; use representation::RDFNodeType; use std::collections::{HashMap, HashSet}; -use std::error::Error; use std::sync::Arc; use virtualization::{Virtualization, VirtualizedDatabase}; use virtualized_query::pushdown_setting::PushdownSetting; @@ -79,7 +78,7 @@ impl Engine { pub async fn query<'py>( &self, query: &str, - ) -> Result<(DataFrame, HashMap, Vec), Box> { + ) -> Result<(DataFrame, HashMap, Vec), ChrontextError> { enable_string_cache(); let parsed_query = parse_sparql_select_query(query)?; debug!("Parsed query: {}", parsed_query.to_string()); @@ -115,14 +114,15 @@ impl Engine { ); let solution_mappings = combiner .combine_static_and_time_series_results(static_queries_map, &preprocessed_query) - .await?; + .await + .map_err(|x| ChrontextError::CombinerError(x))?; let SolutionMappings { mappings, rdf_node_types, } = solution_mappings; Ok(( - mappings.collect()?, + mappings.collect().unwrap(), rdf_node_types, combiner.virtualized_contexts, )) diff --git a/lib/chrontext/src/errors.rs b/lib/chrontext/src/errors.rs index 9bc0a6d..18857b7 100644 --- a/lib/chrontext/src/errors.rs +++ b/lib/chrontext/src/errors.rs @@ -1,30 +1,17 @@ -use std::fmt::{Display, Formatter}; +use crate::combiner::CombinerError; +use crate::splitter::QueryParseError; use thiserror::Error; + #[derive(Debug, Error)] pub enum ChrontextError { - FromJSONFileError(String), + #[error("Missing SPARQL database")] NoSPARQLDatabaseDefined, + #[error("Error creating SPARQL database `{0}`")] CreateSPARQLDatabaseError(String), - DeserializeFromJSONFileError(String), + #[error("No timeseries database defined")] NoTimeseriesDatabaseDefined, -} - -impl Display for ChrontextError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ChrontextError::FromJSONFileError(s) => { - write!(f, "Error reading engine config from JSON: {}", s) - } - ChrontextError::NoSPARQLDatabaseDefined => write!(f, "Missing SPARQL database"), - ChrontextError::CreateSPARQLDatabaseError(s) => { - write!(f, "Error creating SPARQL database {}", s) - } - ChrontextError::DeserializeFromJSONFileError(s) => { - write!(f, "Error deserializing config from JSON file {}", s) - } - ChrontextError::NoTimeseriesDatabaseDefined => { - write!(f, "No timeseries database defined") - } - } - } + #[error(transparent)] + QueryParseError(#[from] QueryParseError), + #[error(transparent)] + CombinerError(#[from] CombinerError), } diff --git a/lib/chrontext/src/sparql_database.rs b/lib/chrontext/src/sparql_database.rs index ba9a3cc..16efe07 100644 --- a/lib/chrontext/src/sparql_database.rs +++ b/lib/chrontext/src/sparql_database.rs @@ -1,12 +1,21 @@ pub mod sparql_embedded_oxigraph; pub mod sparql_endpoint; +use crate::sparql_database::sparql_embedded_oxigraph::EmbeddedOxigraphError; +use crate::sparql_database::sparql_endpoint::SparqlEndpointQueryExecutionError; use async_trait::async_trait; use sparesults::QuerySolution; use spargebra::Query; -use std::error::Error; +use thiserror::Error; +#[derive(Debug, Error)] +pub enum SparqlQueryError { + #[error(transparent)] + EmbeddedOxigraphError(#[from] EmbeddedOxigraphError), + #[error(transparent)] + SparqlEndpointQueryExecutionError(#[from] SparqlEndpointQueryExecutionError), +} #[async_trait] pub trait SparqlQueryable: Send + Sync { - async fn execute(&self, query: &Query) -> Result, Box>; + async fn execute(&self, query: &Query) -> Result, SparqlQueryError>; } diff --git a/lib/chrontext/src/sparql_database/sparql_embedded_oxigraph.rs b/lib/chrontext/src/sparql_database/sparql_embedded_oxigraph.rs index 65d592a..26a3c94 100644 --- a/lib/chrontext/src/sparql_database/sparql_embedded_oxigraph.rs +++ b/lib/chrontext/src/sparql_database/sparql_embedded_oxigraph.rs @@ -1,4 +1,4 @@ -use super::SparqlQueryable; +use super::{SparqlQueryError, SparqlQueryable}; use async_trait::async_trait; use filesize::PathExt; use oxigraph::io::{RdfFormat, RdfParser}; @@ -6,41 +6,27 @@ use oxigraph::sparql::QueryResults; use oxigraph::store::Store; use sparesults::QuerySolution; use spargebra::Query; -use std::error::Error; -use std::fmt::{Display, Formatter}; use std::fs::{read_to_string, File}; use std::io::BufReader; use std::io::Write; use std::path::Path; use std::time::SystemTime; +use thiserror::Error; const RDF_FILE_METADATA: &str = "rdf_file_data.txt"; -#[derive(Debug)] +#[derive(Debug, Error)] pub enum EmbeddedOxigraphError { + #[error("Error opening oxigraph storage path `{0}`")] OpenStorageError(String), + #[error("Error reading NTriples file `{0}`")] ReadNTriplesFileError(String), + #[error("Error reading loading NTriples file `{0}`")] LoaderError(String), + #[error("Oxigraph metadata IO Error `{0}`")] DBMetadataIOError(String), -} - -impl Display for EmbeddedOxigraphError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - EmbeddedOxigraphError::OpenStorageError(o) => { - write!(f, "Error opening oxigraph storage path {}", o) - } - EmbeddedOxigraphError::ReadNTriplesFileError(s) => { - write!(f, "Error reading NTriples file {}", s) - } - EmbeddedOxigraphError::LoaderError(s) => { - write!(f, "Error reading loading NTriples file {}", s) - } - EmbeddedOxigraphError::DBMetadataIOError(s) => { - write!(f, "Oxigraph metadata IO Error {}", s) - } - } - } + #[error("Oxigraph evaluation error")] + EvaluationError(String), } #[derive(Debug)] @@ -56,14 +42,22 @@ pub struct EmbeddedOxigraph { #[async_trait] impl SparqlQueryable for EmbeddedOxigraph { - async fn execute(&self, query: &Query) -> Result, Box> { + async fn execute(&self, query: &Query) -> Result, SparqlQueryError> { let oxiquery = oxigraph::sparql::Query::parse(query.to_string().as_str(), None).unwrap(); - let res = self.store.query(oxiquery).map_err(|x| Box::new(x))?; + let res = self.store.query(oxiquery).map_err(|x| { + SparqlQueryError::EmbeddedOxigraphError(EmbeddedOxigraphError::EvaluationError( + x.to_string(), + )) + })?; match res { QueryResults::Solutions(sols) => { let mut output = vec![]; for s in sols { - output.push(s?); + output.push(s.map_err(|x| { + SparqlQueryError::EmbeddedOxigraphError( + EmbeddedOxigraphError::EvaluationError(x.to_string()), + ) + })?); } Ok(output) } diff --git a/lib/chrontext/src/sparql_database/sparql_endpoint.rs b/lib/chrontext/src/sparql_database/sparql_endpoint.rs index e0715b3..cb20bf9 100644 --- a/lib/chrontext/src/sparql_database/sparql_endpoint.rs +++ b/lib/chrontext/src/sparql_database/sparql_endpoint.rs @@ -1,52 +1,33 @@ -use super::SparqlQueryable; +use super::{SparqlQueryError, SparqlQueryable}; use async_trait::async_trait; use reqwest::header::{ACCEPT, USER_AGENT}; -use reqwest::StatusCode; use sparesults::{ FromReadQueryResultsReader, QueryResultsFormat, QueryResultsParseError, QueryResultsParser, QuerySolution, }; use spargebra::Query; -use std::error::Error; -use std::fmt::{Display, Formatter}; +use thiserror::Error; -#[derive(Debug)] -pub enum QueryExecutionError { +#[derive(Debug, Error)] +pub enum SparqlEndpointQueryExecutionError { + #[error(transparent)] RequestError(reqwest::Error), - BadStatusCode(StatusCode), + #[error("Bad status code `{0}`")] + BadStatusCode(String), + #[error("Results parse error `{0}`")] ResultsParseError(QueryResultsParseError), + #[error("Solution parse error `{0}`")] SolutionParseError(QueryResultsParseError), + #[error("Wrong result type, expected solutions")] WrongResultType, } -impl Display for QueryExecutionError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match &self { - QueryExecutionError::RequestError(reqerr) => std::fmt::Display::fmt(&reqerr, f), - QueryExecutionError::BadStatusCode(status_code) => { - std::fmt::Display::fmt(&status_code, f) - } - QueryExecutionError::ResultsParseError(parseerr) => { - std::fmt::Display::fmt(&parseerr, f) - } - QueryExecutionError::SolutionParseError(parseerr) => { - std::fmt::Display::fmt(&parseerr, f) - } - QueryExecutionError::WrongResultType => { - write!(f, "Wrong result type, expected solutions") - } - } - } -} - -impl Error for QueryExecutionError {} - pub struct SparqlEndpoint { pub endpoint: String, } #[async_trait] impl SparqlQueryable for SparqlEndpoint { - async fn execute(&self, query: &Query) -> Result, Box> { + async fn execute(&self, query: &Query) -> Result, SparqlQueryError> { let client = reqwest::Client::new(); let response = client .get(&self.endpoint) @@ -59,9 +40,10 @@ impl SparqlQueryable for SparqlEndpoint { match response { Ok(proper_response) => { if proper_response.status().as_u16() != 200 { - Err(Box::new(QueryExecutionError::BadStatusCode( - proper_response.status(), - ))) + Err(SparqlEndpointQueryExecutionError::BadStatusCode( + proper_response.status().to_string(), + ) + .into()) } else { let text = proper_response.text().await.expect("Read text error"); let json_parser = QueryResultsParser::from_format(QueryResultsFormat::Json); @@ -73,27 +55,27 @@ impl SparqlQueryable for SparqlEndpoint { for s in solutions { match s { Ok(query_solution) => solns.push(query_solution), - Err(parse_error) => { - return Err(Box::new( - QueryExecutionError::SolutionParseError( - parse_error, - ), - )) - } + Err(parse_error) => return Err( + SparqlEndpointQueryExecutionError::SolutionParseError( + parse_error, + ) + .into(), + ), } } Ok(solns) } else { - Err(Box::new(QueryExecutionError::WrongResultType)) + Err(SparqlEndpointQueryExecutionError::WrongResultType.into()) } } - Err(parse_error) => Err(Box::new(QueryExecutionError::ResultsParseError( - parse_error, - ))), + Err(parse_error) => Err( + SparqlEndpointQueryExecutionError::ResultsParseError(parse_error) + .into(), + ), } } } - Err(error) => Err(Box::new(QueryExecutionError::RequestError(error))), + Err(error) => Err(SparqlEndpointQueryExecutionError::RequestError(error).into()), } } } diff --git a/lib/chrontext/src/splitter.rs b/lib/chrontext/src/splitter.rs index 32cd422..8d6cf86 100644 --- a/lib/chrontext/src/splitter.rs +++ b/lib/chrontext/src/splitter.rs @@ -1,36 +1,17 @@ use spargebra::{Query, SparqlSyntaxError}; -use std::error::Error; -use std::fmt::{Debug, Display, Formatter}; +use thiserror::Error; -#[derive(Debug)] -pub enum SelectQueryErrorKind { +#[derive(Debug, Error)] +pub enum QueryParseError { + #[error(transparent)] Parse(SparqlSyntaxError), + #[error("Not a select query")] NotSelectQuery, + #[error("Unsupported construct: `{0}`")] Unsupported(String), } -#[derive(Debug)] -pub struct SelectQueryError { - kind: SelectQueryErrorKind, -} - -impl Display for SelectQueryError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match &self.kind { - SelectQueryErrorKind::Parse(pe) => std::fmt::Display::fmt(&pe, f), - SelectQueryErrorKind::NotSelectQuery => { - write!(f, "Not a select query") - } - SelectQueryErrorKind::Unsupported(s) => { - write!(f, "Unsupported construct: {}", s) - } - } - } -} - -impl Error for SelectQueryError {} - -pub fn parse_sparql_select_query(query_str: &str) -> Result { +pub fn parse_sparql_select_query(query_str: &str) -> Result { let q_res = Query::parse(query_str, None); match q_res { Ok(q) => match q { @@ -47,9 +28,9 @@ pub fn parse_sparql_select_query(query_str: &str) -> Result 0 { - Err(SelectQueryError { - kind: SelectQueryErrorKind::Unsupported(unsupported_constructs.join(",")), - }) + Err(QueryParseError::Unsupported( + unsupported_constructs.join(","), + )) } else { Ok(Query::Select { dataset, @@ -58,12 +39,8 @@ pub fn parse_sparql_select_query(query_str: &str) -> Result Err(SelectQueryError { - kind: SelectQueryErrorKind::NotSelectQuery, - }), + _ => Err(QueryParseError::NotSelectQuery), }, - Err(e) => Err(SelectQueryError { - kind: SelectQueryErrorKind::Parse(e), - }), + Err(e) => Err(QueryParseError::Parse(e)), } } diff --git a/lib/virtualization/src/bigquery.rs b/lib/virtualization/src/bigquery.rs index 39a0826..2518a84 100644 --- a/lib/virtualization/src/bigquery.rs +++ b/lib/virtualization/src/bigquery.rs @@ -1,4 +1,4 @@ -use crate::errors::VirtualizedDatabaseError; +use crate::errors::ChrontextError; use crate::python::translate_sql; use crate::{get_datatype_map, Dialect}; use bigquery_polars::{BigQueryExecutor, Client}; @@ -35,7 +35,7 @@ impl VirtualizedBigQueryDatabase { pub async fn query( &self, vq: &VirtualizedQuery, - ) -> Result { + ) -> Result { let mut rename_map = HashMap::new(); let new_vq = rename_non_alpha_vars(vq.clone(), &mut rename_map); let query_string = diff --git a/lib/virtualization/src/errors.rs b/lib/virtualization/src/errors.rs index 6fe806c..8770d47 100644 --- a/lib/virtualization/src/errors.rs +++ b/lib/virtualization/src/errors.rs @@ -6,7 +6,7 @@ use thiserror::*; use url::ParseError; #[derive(Error, Debug)] -pub enum VirtualizedDatabaseError { +pub enum ChrontextError { #[error(transparent)] PyVirtualizedDatabaseError(#[from] PyErr), #[error(transparent)] diff --git a/lib/virtualization/src/lib.rs b/lib/virtualization/src/lib.rs index b860d05..4109cbe 100644 --- a/lib/virtualization/src/lib.rs +++ b/lib/virtualization/src/lib.rs @@ -5,7 +5,7 @@ pub mod bigquery; pub mod opcua; use crate::bigquery::VirtualizedBigQueryDatabase; -use crate::errors::VirtualizedDatabaseError; +use crate::errors::ChrontextError; use crate::opcua::VirtualizedOPCUADatabase; use crate::python::VirtualizedPythonDatabase; use oxrdf::NamedNode; @@ -102,10 +102,10 @@ impl VirtualizedDatabase { pub async fn query( &self, vq: &VirtualizedQuery, - ) -> Result { + ) -> Result { match self { VirtualizedDatabase::VirtualizedPythonDatabase(pyvdb) => { - let df = pyvdb.query(vq).map_err(VirtualizedDatabaseError::from)?; + let df = pyvdb.query(vq).map_err(ChrontextError::from)?; let rdf_node_types = get_datatype_map(&df); Ok(EagerSolutionMappings::new(df, rdf_node_types)) } diff --git a/lib/virtualization/src/opcua.rs b/lib/virtualization/src/opcua.rs index 239e5aa..be6dcb4 100644 --- a/lib/virtualization/src/opcua.rs +++ b/lib/virtualization/src/opcua.rs @@ -1,4 +1,4 @@ -use crate::errors::VirtualizedDatabaseError; +use crate::errors::ChrontextError; use crate::get_datatype_map; use chrono::NaiveDateTime; use opcua::client::prelude::{ @@ -79,7 +79,7 @@ impl VirtualizedOPCUADatabase { pub async fn query( &self, vq: &VirtualizedQuery, - ) -> Result { + ) -> Result { validate_vq(vq, true, false)?; let session = self.session.write(); let start_time = find_time(vq, &FindTime::Start); @@ -269,21 +269,19 @@ fn validate_vq( vq: &VirtualizedQuery, toplevel: bool, inside_grouping: bool, -) -> Result<(), VirtualizedDatabaseError> { +) -> Result<(), ChrontextError> { match vq { //Todo add validation when basic has grouped.. VirtualizedQuery::Basic(_) => Ok(()), VirtualizedQuery::Filtered(f, _) => validate_vq(f, false, inside_grouping), VirtualizedQuery::Grouped(g) => { if !toplevel { - Err(VirtualizedDatabaseError::VirtualizedQueryTypeNotSupported) + Err(ChrontextError::VirtualizedQueryTypeNotSupported) } else { validate_vq(&g.vq, false, true) } } - VirtualizedQuery::InnerJoin(_, _) => { - Err(VirtualizedDatabaseError::VirtualizedQueryTypeNotSupported) - } + VirtualizedQuery::InnerJoin(_, _) => Err(ChrontextError::VirtualizedQueryTypeNotSupported), VirtualizedQuery::ExpressionAs(t, _, _) => validate_vq(t, false, inside_grouping), VirtualizedQuery::Sliced(..) => todo!(), VirtualizedQuery::Ordered(_, _) => todo!(), @@ -728,20 +726,20 @@ fn from_numeric_datatype(lit: &Literal) -> Option { } } -fn node_id_from_string(s: &str) -> Result { +fn node_id_from_string(s: &str) -> Result { let mut splitstring = s.split(";"); let ns_str = if let Some(ns_str) = splitstring.next() { ns_str } else { - return Err(VirtualizedDatabaseError::InvalidNodeIdError(s.to_string())); + return Err(ChrontextError::InvalidNodeIdError(s.to_string())); }; let identifier_string = splitstring.collect::>().join(";"); let namespace: u16 = if let Some(namespace_str) = ns_str.strip_prefix("ns=") { namespace_str .parse() - .map_err(|_| VirtualizedDatabaseError::InvalidNodeIdError(s.to_string()))? + .map_err(|_| ChrontextError::InvalidNodeIdError(s.to_string()))? } else { - return Err(VirtualizedDatabaseError::InvalidNodeIdError(s.to_string())); + return Err(ChrontextError::InvalidNodeIdError(s.to_string())); }; if identifier_string.starts_with("s=") { let identifier = identifier_string.strip_prefix("s=").unwrap(); @@ -754,7 +752,7 @@ fn node_id_from_string(s: &str) -> Result { .strip_prefix("i=") .unwrap() .parse() - .map_err(|_| VirtualizedDatabaseError::InvalidNodeIdError(s.to_string()))?; + .map_err(|_| ChrontextError::InvalidNodeIdError(s.to_string()))?; Ok(NodeId { namespace, identifier: Identifier::Numeric(identifier), @@ -765,7 +763,7 @@ fn node_id_from_string(s: &str) -> Result { namespace, identifier: Identifier::Guid( Guid::from_str(identifier) - .map_err(|_| VirtualizedDatabaseError::InvalidNodeIdError(s.to_string()))?, + .map_err(|_| ChrontextError::InvalidNodeIdError(s.to_string()))?, ), }) } else if identifier_string.starts_with("b=") { @@ -773,13 +771,13 @@ fn node_id_from_string(s: &str) -> Result { let byte_string = if let Some(byte_string) = ByteString::from_base64(identifier) { byte_string } else { - return Err(VirtualizedDatabaseError::InvalidNodeIdError(s.to_string())); + return Err(ChrontextError::InvalidNodeIdError(s.to_string())); }; Ok(NodeId { namespace, identifier: Identifier::ByteString(byte_string), }) } else { - Err(VirtualizedDatabaseError::InvalidNodeIdError(s.to_string())) + Err(ChrontextError::InvalidNodeIdError(s.to_string())) } } diff --git a/py_chrontext/src/errors.rs b/py_chrontext/src/errors.rs index 5b075df..dbd0213 100644 --- a/py_chrontext/src/errors.rs +++ b/py_chrontext/src/errors.rs @@ -1,4 +1,6 @@ +use chrontext::combiner::CombinerError; use chrontext::errors::ChrontextError as RustChrontextError; +use chrontext::splitter::QueryParseError; use oxrdf::IriParseError; use pyo3::{create_exception, exceptions::PyException, prelude::*}; use spargebra::SparqlSyntaxError; @@ -10,8 +12,6 @@ pub enum PyChrontextError { DatatypeIRIParseError(#[from] IriParseError), #[error(transparent)] DataProductQueryParseError(#[from] SparqlSyntaxError), - #[error(transparent)] - QueryExecutionError(Box), #[error("Missing SPARQL database")] MissingSPARQLDatabaseError, #[error("SPARQL database defined multiple times")] @@ -31,9 +31,6 @@ impl std::convert::From for PyErr { PyChrontextError::DatatypeIRIParseError(err) => { DatatypeIRIParseError::new_err(format!("{}", err)) } - PyChrontextError::QueryExecutionError(err) => { - QueryExecutionError::new_err(format!("{}", err)) - } PyChrontextError::MissingSPARQLDatabaseError => MissingSPARQLDatabaseError::new_err(""), PyChrontextError::MultipleSPARQLDatabasesError => { MultipleSPARQLDatabasesError::new_err("") @@ -53,7 +50,6 @@ impl std::convert::From for PyErr { create_exception!(exceptions, DatatypeIRIParseError, PyException); create_exception!(exceptions, DataProductQueryParseError, PyException); -create_exception!(exceptions, QueryExecutionError, PyException); create_exception!(exceptions, MissingSPARQLDatabaseError, PyException); create_exception!(exceptions, MultipleSPARQLDatabasesError, PyException); create_exception!(exceptions, MissingVirtualizedDatabaseError, PyException); diff --git a/py_chrontext/src/lib.rs b/py_chrontext/src/lib.rs index 88fc0d4..f9c09ed 100644 --- a/py_chrontext/src/lib.rs +++ b/py_chrontext/src/lib.rs @@ -176,11 +176,13 @@ impl PyEngine { let mut builder = Builder::new_multi_thread(); builder.enable_all(); - let (mut df, mut datatypes, pushdown_contexts) = builder - .build() - .unwrap() - .block_on(self.engine.as_mut().unwrap().query(sparql)) - .map_err(|err| PyChrontextError::QueryExecutionError(err))?; + let (mut df, mut datatypes, pushdown_contexts) = py.allow_threads(move || { + builder + .build() + .unwrap() + .block_on(self.engine.as_mut().unwrap().query(sparql)) + .map_err(|err| PyChrontextError::ChrontextError(err)) + })?; (df, datatypes) = fix_cats_and_multicolumns(df, datatypes, native_dataframe.unwrap_or(false));