#[cfg(feature = "star")]
use oxirs_star::{StarConfig, StarError, StarResult, StarStore, StarTerm, StarTriple};
use crate::algebra::{Literal, Term, TriplePattern};
use anyhow::Result;
use oxirs_core::model::{NamedNode, Variable};
use std::collections::HashMap;
#[cfg(feature = "star")]
pub struct SparqlStarExecutor {
star_store: StarStore,
#[allow(dead_code)]
config: StarConfig,
}
#[cfg(feature = "star")]
impl SparqlStarExecutor {
pub fn new() -> Self {
Self {
star_store: StarStore::new(),
config: StarConfig::default(),
}
}
pub fn with_config(config: StarConfig) -> Self {
Self {
star_store: StarStore::with_config(config.clone()),
config,
}
}
pub fn store(&self) -> &StarStore {
&self.star_store
}
pub fn store_mut(&mut self) -> &mut StarStore {
&mut self.star_store
}
pub fn term_to_star_term(term: &Term) -> StarResult<StarTerm> {
match term {
Term::Iri(iri) => StarTerm::iri(iri.as_str())
.map_err(|e| StarError::invalid_term_type(format!("Invalid IRI: {}", e))),
Term::Literal(lit) => {
if let Some(ref lang) = lit.language {
StarTerm::literal_with_language(&lit.value, lang).map_err(|e| {
StarError::invalid_term_type(format!("Invalid literal: {}", e))
})
} else if let Some(ref datatype) = lit.datatype {
StarTerm::literal_with_datatype(&lit.value, datatype.as_str()).map_err(|e| {
StarError::invalid_term_type(format!("Invalid literal: {}", e))
})
} else {
StarTerm::literal(&lit.value).map_err(|e| {
StarError::invalid_term_type(format!("Invalid literal: {}", e))
})
}
}
Term::BlankNode(id) => StarTerm::blank_node(id)
.map_err(|e| StarError::invalid_term_type(format!("Invalid blank node: {}", e))),
Term::QuotedTriple(triple) => {
let star_triple = Self::triple_pattern_to_star_triple(triple)?;
Ok(StarTerm::quoted_triple(star_triple))
}
Term::Variable(var) => {
Err(StarError::invalid_term_type(format!(
"Cannot convert variable {} to StarTerm",
var
)))
}
Term::PropertyPath(_) => Err(StarError::invalid_term_type(
"Cannot convert property path to StarTerm".to_string(),
)),
}
}
pub fn triple_pattern_to_star_triple(pattern: &TriplePattern) -> StarResult<StarTriple> {
let subject = Self::term_to_star_term(&pattern.subject)?;
let predicate = Self::term_to_star_term(&pattern.predicate)?;
let object = Self::term_to_star_term(&pattern.object)?;
let triple = StarTriple::new(subject, predicate, object);
triple.validate()?;
Ok(triple)
}
pub fn star_term_to_term(star_term: &StarTerm) -> Result<Term> {
match star_term {
StarTerm::NamedNode(node) => Ok(Term::Iri(NamedNode::new(&node.iri)?)),
StarTerm::Literal(lit) => {
let literal = if let Some(ref lang) = lit.language {
Literal::with_language(lit.value.clone(), lang.clone())
} else if let Some(ref datatype) = lit.datatype {
Literal::new(
lit.value.clone(),
None,
Some(NamedNode::new(&datatype.iri)?),
)
} else {
Literal::new(lit.value.clone(), None, None)
};
Ok(Term::Literal(literal))
}
StarTerm::BlankNode(bn) => Ok(Term::BlankNode(bn.id.clone())),
StarTerm::QuotedTriple(triple) => {
let subject = Self::star_term_to_term(&triple.subject)?;
let predicate = Self::star_term_to_term(&triple.predicate)?;
let object = Self::star_term_to_term(&triple.object)?;
Ok(Term::QuotedTriple(Box::new(TriplePattern::new(
subject, predicate, object,
))))
}
StarTerm::Variable(var) => {
Err(anyhow::anyhow!(
"Cannot convert StarTerm::Variable {} to ARQ Term",
var.name
))
}
}
}
pub fn execute_sparql_star_query(&self, query: &str) -> Result<Vec<HashMap<Variable, Term>>> {
use oxirs_star::query::{QueryExecutor, QueryParser};
let mut executor = QueryExecutor::new(self.star_store.clone());
let (select_vars, bgp) = QueryParser::parse_simple_select(query)
.map_err(|e| anyhow::anyhow!("SPARQL-star parsing failed: {}", e))?;
let star_results = executor
.execute_select(&bgp, &select_vars)
.map_err(|e| anyhow::anyhow!("SPARQL-star execution failed: {}", e))?;
let mut results = Vec::new();
for star_binding in star_results {
let mut binding = HashMap::new();
for (var_name, star_term) in star_binding {
let variable = Variable::new(&var_name)?;
let term = Self::star_term_to_term(&star_term)?;
binding.insert(variable, term);
}
results.push(binding);
}
Ok(results)
}
}
#[cfg(feature = "star")]
impl Default for SparqlStarExecutor {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "star")]
pub mod sparql_star_functions {
use super::*;
pub fn is_quoted_triple(term: &Term) -> bool {
matches!(term, Term::QuotedTriple(_))
}
pub fn get_subject(term: &Term) -> Option<&Term> {
if let Term::QuotedTriple(triple) = term {
Some(&triple.subject)
} else {
None
}
}
pub fn get_predicate(term: &Term) -> Option<&Term> {
if let Term::QuotedTriple(triple) = term {
Some(&triple.predicate)
} else {
None
}
}
pub fn get_object(term: &Term) -> Option<&Term> {
if let Term::QuotedTriple(triple) = term {
Some(&triple.object)
} else {
None
}
}
}
#[cfg(feature = "star")]
pub mod pattern_matching {
use super::*;
pub fn has_quoted_triples(pattern: &TriplePattern) -> bool {
matches!(pattern.subject, Term::QuotedTriple(_))
|| matches!(pattern.object, Term::QuotedTriple(_))
}
pub fn extract_quoted_triples(term: &Term) -> Vec<TriplePattern> {
let mut triples = Vec::new();
extract_quoted_triples_recursive(term, &mut triples);
triples
}
fn extract_quoted_triples_recursive(term: &Term, triples: &mut Vec<TriplePattern>) {
if let Term::QuotedTriple(triple) = term {
triples.push((**triple).clone());
extract_quoted_triples_recursive(&triple.subject, triples);
extract_quoted_triples_recursive(&triple.predicate, triples);
extract_quoted_triples_recursive(&triple.object, triples);
}
}
pub fn nesting_depth(term: &Term) -> usize {
match term {
Term::QuotedTriple(triple) => {
let subject_depth = nesting_depth(&triple.subject);
let predicate_depth = nesting_depth(&triple.predicate);
let object_depth = nesting_depth(&triple.object);
1 + subject_depth.max(predicate_depth).max(object_depth)
}
_ => 0,
}
}
}
#[cfg(feature = "star")]
pub mod star_statistics {
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SparqlStarStatistics {
pub quoted_patterns_matched: usize,
pub max_nesting_depth: usize,
pub star_functions_evaluated: usize,
pub execution_time_us: u64,
pub result_count: usize,
}
impl SparqlStarStatistics {
pub fn new() -> Self {
Self::default()
}
pub fn record_quoted_pattern(&mut self, depth: usize) {
self.quoted_patterns_matched += 1;
self.max_nesting_depth = self.max_nesting_depth.max(depth);
}
pub fn record_star_function(&mut self) {
self.star_functions_evaluated += 1;
}
pub fn record_execution_time(&mut self, duration_us: u64) {
self.execution_time_us = duration_us;
}
pub fn record_results(&mut self, count: usize) {
self.result_count = count;
}
pub fn avg_time_per_result(&self) -> Option<f64> {
if self.result_count > 0 {
Some(self.execution_time_us as f64 / self.result_count as f64)
} else {
None
}
}
}
}
#[cfg(test)]
#[cfg(feature = "star")]
mod tests {
use super::*;
#[test]
fn test_sparql_star_executor_creation() {
let executor = SparqlStarExecutor::new();
assert_eq!(executor.store().len(), 0);
}
#[test]
fn test_term_conversion() {
let iri_term = Term::Iri(NamedNode::new("http://example.org/test").unwrap());
let star_term = SparqlStarExecutor::term_to_star_term(&iri_term).unwrap();
assert!(star_term.is_named_node());
let converted_back = SparqlStarExecutor::star_term_to_term(&star_term).unwrap();
assert!(matches!(converted_back, Term::Iri(_)));
}
#[test]
fn test_quoted_triple_conversion() {
let subject = Term::Iri(NamedNode::new("http://example.org/s").unwrap());
let predicate = Term::Iri(NamedNode::new("http://example.org/p").unwrap());
let object = Term::Iri(NamedNode::new("http://example.org/o").unwrap());
let pattern = TriplePattern::new(subject, predicate, object);
let star_triple = SparqlStarExecutor::triple_pattern_to_star_triple(&pattern).unwrap();
assert!(star_triple.subject.is_named_node());
assert!(star_triple.predicate.is_named_node());
assert!(star_triple.object.is_named_node());
}
#[test]
fn test_nesting_depth_calculation() {
use pattern_matching::nesting_depth;
let simple_term = Term::Iri(NamedNode::new("http://example.org/test").unwrap());
assert_eq!(nesting_depth(&simple_term), 0);
let inner_triple = TriplePattern::new(
Term::Iri(NamedNode::new("http://example.org/s").unwrap()),
Term::Iri(NamedNode::new("http://example.org/p").unwrap()),
Term::Iri(NamedNode::new("http://example.org/o").unwrap()),
);
let quoted_term = Term::QuotedTriple(Box::new(inner_triple));
assert_eq!(nesting_depth("ed_term), 1);
let nested_triple = TriplePattern::new(
quoted_term.clone(),
Term::Iri(NamedNode::new("http://example.org/certainty").unwrap()),
Term::Literal(Literal::new("0.9".to_string(), None, None)),
);
let nested_term = Term::QuotedTriple(Box::new(nested_triple));
assert_eq!(nesting_depth(&nested_term), 2);
}
#[test]
fn test_statistics_tracking() {
use star_statistics::SparqlStarStatistics;
let mut stats = SparqlStarStatistics::new();
assert_eq!(stats.quoted_patterns_matched, 0);
stats.record_quoted_pattern(1);
stats.record_quoted_pattern(2);
stats.record_star_function();
stats.record_execution_time(1000);
stats.record_results(10);
assert_eq!(stats.quoted_patterns_matched, 2);
assert_eq!(stats.max_nesting_depth, 2);
assert_eq!(stats.star_functions_evaluated, 1);
assert_eq!(stats.execution_time_us, 1000);
assert_eq!(stats.result_count, 10);
assert_eq!(stats.avg_time_per_result(), Some(100.0));
}
#[test]
fn test_sparql_star_utility_functions() {
use sparql_star_functions::*;
let subject = Term::Iri(NamedNode::new("http://example.org/s").unwrap());
let predicate = Term::Iri(NamedNode::new("http://example.org/p").unwrap());
let object = Term::Literal(Literal::new("value".to_string(), None, None));
let triple_pattern = TriplePattern::new(subject.clone(), predicate.clone(), object.clone());
let quoted_term = Term::QuotedTriple(Box::new(triple_pattern));
assert!(is_quoted_triple("ed_term));
assert!(!is_quoted_triple(&subject));
assert!(get_subject("ed_term).is_some());
assert!(get_predicate("ed_term).is_some());
assert!(get_object("ed_term).is_some());
assert!(get_subject(&subject).is_none());
}
}