Skip to main content

oxirs_core/sparql/
executor.rs

1//! SPARQL query execution engine
2//!
3//! This module provides the core query execution logic for SPARQL queries.
4//! It delegates to parser, pattern, filter, expression, aggregate, and modifier modules.
5//!
6//! ## Performance Monitoring
7//!
8//! The executor integrates SciRS2-core metrics for comprehensive query performance tracking:
9//! - Query execution time (per query type: SELECT, ASK, CONSTRUCT, DESCRIBE)
10//! - Pattern matching operations
11//! - Result set sizes
12//! - Query complexity metrics
13//!
14//! ## Module layout
15//!
16//! - `executor.rs` (this file): struct definition, `new()`, `get_stats()`, `execute()`/`query()` dispatcher
17//! - `executor_query_handlers.rs`: all `execute_*` method implementations and helpers (declared in `mod.rs`)
18//! - `executor_tests.rs`: unit tests (declared in `mod.rs`)
19
20use super::*;
21use crate::rdf_store::{OxirsQueryResults, StorageBackend};
22use crate::{OxirsError, Result};
23use scirs2_core::metrics::{Counter, Histogram, Timer};
24use std::collections::HashMap;
25use std::sync::Arc;
26
27/// Executor performance statistics
28#[derive(Debug, Clone)]
29pub struct ExecutorStats {
30    /// Total number of queries executed
31    pub total_queries: u64,
32    /// Number of SELECT queries
33    pub select_queries: u64,
34    /// Number of ASK queries
35    pub ask_queries: u64,
36    /// Number of CONSTRUCT queries
37    pub construct_queries: u64,
38    /// Number of DESCRIBE queries
39    pub describe_queries: u64,
40    /// Total pattern matching operations
41    pub pattern_matches: u64,
42    /// Average execution time in seconds
43    pub avg_execution_time_secs: f64,
44    /// Total number of observations
45    pub total_observations: u64,
46}
47
48/// VALUES clause for inline data (SPARQL-specific)
49#[derive(Debug, Clone)]
50pub(crate) struct ValuesClause {
51    pub(crate) variables: Vec<String>,
52    pub(crate) rows: Vec<Vec<String>>,
53}
54
55/// SPARQL query executor with integrated performance monitoring
56///
57/// This executor tracks query performance metrics using SciRS2-core:
58/// - Query execution time broken down by query type
59/// - Pattern matching statistics
60/// - Result set size distribution
61/// - Query complexity indicators
62pub struct QueryExecutor<'a> {
63    pub(crate) backend: &'a StorageBackend,
64    /// Query execution timer
65    pub(crate) query_timer: Arc<Timer>,
66    /// SELECT query counter
67    pub(crate) select_counter: Arc<Counter>,
68    /// ASK query counter
69    pub(crate) ask_counter: Arc<Counter>,
70    /// CONSTRUCT query counter
71    pub(crate) construct_counter: Arc<Counter>,
72    /// DESCRIBE query counter
73    pub(crate) describe_counter: Arc<Counter>,
74    /// Pattern matching counter
75    pub(crate) pattern_counter: Arc<Counter>,
76    /// Result set size histogram
77    pub(crate) result_size_histogram: Arc<Histogram>,
78}
79
80impl<'a> QueryExecutor<'a> {
81    /// Create a new query executor for the given storage backend with performance monitoring
82    ///
83    /// The executor automatically tracks:
84    /// - Query execution times
85    /// - Query type distribution
86    /// - Pattern matching operations
87    /// - Result set sizes
88    pub fn new(backend: &'a StorageBackend) -> Self {
89        Self {
90            backend,
91            query_timer: Arc::new(Timer::new("query_execution_time".to_string())),
92            select_counter: Arc::new(Counter::new("select_queries".to_string())),
93            ask_counter: Arc::new(Counter::new("ask_queries".to_string())),
94            construct_counter: Arc::new(Counter::new("construct_queries".to_string())),
95            describe_counter: Arc::new(Counter::new("describe_queries".to_string())),
96            pattern_counter: Arc::new(Counter::new("pattern_matches".to_string())),
97            result_size_histogram: Arc::new(Histogram::new("result_set_size".to_string())),
98        }
99    }
100
101    /// Get query execution statistics
102    ///
103    /// Returns the current metrics including:
104    /// - Total queries executed by type
105    /// - Average execution time
106    /// - Pattern matching statistics
107    /// - Result set size distribution
108    pub fn get_stats(&self) -> ExecutorStats {
109        let select = self.select_counter.get();
110        let ask = self.ask_counter.get();
111        let construct = self.construct_counter.get();
112        let describe = self.describe_counter.get();
113
114        let timer_stats = self.query_timer.get_stats();
115
116        ExecutorStats {
117            total_queries: select + ask + construct + describe,
118            select_queries: select,
119            ask_queries: ask,
120            construct_queries: construct,
121            describe_queries: describe,
122            pattern_matches: self.pattern_counter.get(),
123            avg_execution_time_secs: timer_stats.mean,
124            total_observations: timer_stats.count,
125        }
126    }
127
128    /// Execute a SPARQL query
129    pub fn execute(&self, sparql: &str) -> Result<OxirsQueryResults> {
130        self.query(sparql)
131    }
132
133    /// Execute a SPARQL query (main dispatcher)
134    pub fn query(&self, sparql: &str) -> Result<OxirsQueryResults> {
135        let start = std::time::Instant::now();
136
137        let sparql = sparql.trim();
138
139        let (prefixes, expanded_query) = self.extract_and_expand_prefixes(sparql)?;
140
141        let query_to_execute = if !prefixes.is_empty() {
142            &expanded_query
143        } else {
144            sparql
145        };
146
147        let result = if query_to_execute.to_uppercase().contains("SELECT") {
148            self.select_counter.inc();
149            self.execute_select_query(query_to_execute)
150        } else if query_to_execute.to_uppercase().starts_with("ASK") {
151            self.ask_counter.inc();
152            self.execute_ask_query(query_to_execute)
153        } else if query_to_execute.to_uppercase().starts_with("CONSTRUCT") {
154            self.construct_counter.inc();
155            self.execute_construct_query(query_to_execute)
156        } else if query_to_execute.to_uppercase().starts_with("DESCRIBE") {
157            self.describe_counter.inc();
158            self.execute_describe_query(query_to_execute)
159        } else {
160            return Err(OxirsError::Query(format!(
161                "Unsupported SPARQL query type: {sparql}"
162            )));
163        };
164
165        let duration = start.elapsed();
166        self.query_timer.observe(duration);
167
168        if let Ok(ref query_result) = result {
169            self.result_size_histogram
170                .observe(query_result.len() as f64);
171        }
172
173        result
174    }
175
176    /// Extract and expand PREFIX declarations
177    fn extract_and_expand_prefixes(
178        &self,
179        sparql: &str,
180    ) -> Result<(HashMap<String, String>, String)> {
181        extract_and_expand_prefixes(sparql)
182    }
183}