Skip to main content

oxigdal_query/
lib.rs

1//! SQL-like query language and cost-based optimizer for geospatial data.
2//!
3//! This crate provides a complete query engine with:
4//! - SQL-like query language parsing
5//! - Cost-based query optimization
6//! - Parallel query execution
7//! - Result caching
8//! - Index selection
9//!
10//! # Example
11//!
12//! ```no_run
13//! use oxigdal_query::*;
14//!
15//! # async fn example() -> std::result::Result<(), Box<dyn std::error::Error>> {
16//! // Parse SQL query
17//! let sql = "SELECT id, name FROM users WHERE age > 18";
18//! let statement = parser::sql::parse_sql(sql)?;
19//!
20//! // Optimize query
21//! let optimizer = optimizer::Optimizer::new();
22//! let optimized = optimizer.optimize(statement)?;
23//!
24//! // Execute query
25//! let mut executor = executor::Executor::new();
26//! // Register data sources...
27//! let results = executor.execute(&optimized.statement).await?;
28//! # Ok(())
29//! # }
30//! ```
31
32#![warn(missing_docs)]
33#![warn(clippy::all)]
34#![allow(clippy::module_name_repetitions)]
35
36pub mod cache;
37pub mod error;
38pub mod executor;
39pub mod explain;
40pub mod index;
41pub mod optimizer;
42pub mod parallel;
43pub mod parser;
44
45pub use cache::{CacheConfig, QueryCache};
46pub use error::{QueryError, Result};
47pub use executor::Executor;
48pub use explain::ExplainPlan;
49pub use optimizer::{OptimizedQuery, Optimizer, OptimizerConfig};
50pub use parser::{Statement, parse_sql};
51
52/// Query engine that combines all components.
53pub struct QueryEngine {
54    /// Parser (stateless, no storage needed).
55    _parser_marker: std::marker::PhantomData<()>,
56    /// Optimizer.
57    optimizer: Optimizer,
58    /// Executor.
59    executor: Executor,
60    /// Cache.
61    cache: QueryCache,
62}
63
64impl QueryEngine {
65    /// Create a new query engine with default configuration.
66    pub fn new() -> Self {
67        Self {
68            _parser_marker: std::marker::PhantomData,
69            optimizer: Optimizer::new(),
70            executor: Executor::new(),
71            cache: QueryCache::new(CacheConfig::default()),
72        }
73    }
74
75    /// Create a new query engine with custom configuration.
76    pub fn with_config(optimizer_config: OptimizerConfig, cache_config: CacheConfig) -> Self {
77        Self {
78            _parser_marker: std::marker::PhantomData,
79            optimizer: Optimizer::with_config(optimizer_config),
80            executor: Executor::new(),
81            cache: QueryCache::new(cache_config),
82        }
83    }
84
85    /// Get the optimizer.
86    pub fn optimizer(&self) -> &Optimizer {
87        &self.optimizer
88    }
89
90    /// Get the executor.
91    pub fn executor(&mut self) -> &mut Executor {
92        &mut self.executor
93    }
94
95    /// Get the cache.
96    pub fn cache(&self) -> &QueryCache {
97        &self.cache
98    }
99
100    /// Execute a SQL query.
101    pub async fn execute_sql(&mut self, sql: &str) -> Result<Vec<executor::scan::RecordBatch>> {
102        // Parse SQL
103        let statement = parse_sql(sql)?;
104
105        // Check cache
106        if let Some(cached) = self.cache.get(&statement) {
107            return Ok(cached);
108        }
109
110        // Optimize
111        let optimized = self.optimizer.optimize(statement.clone())?;
112
113        // Execute
114        let results = self.executor.execute(&optimized.statement).await?;
115
116        // Cache results
117        self.cache.put(&statement, results.clone());
118
119        Ok(results)
120    }
121
122    /// Explain a SQL query.
123    pub fn explain_sql(&self, sql: &str) -> Result<ExplainPlan> {
124        let statement = parse_sql(sql)?;
125        let optimized = self.optimizer.optimize(statement)?;
126        Ok(ExplainPlan::from_optimized(&optimized))
127    }
128
129    /// Register a data source.
130    pub fn register_data_source(
131        &mut self,
132        name: String,
133        source: std::sync::Arc<dyn executor::scan::DataSource>,
134    ) {
135        self.executor.register_data_source(name, source);
136    }
137
138    /// Clear the query cache.
139    pub fn clear_cache(&self) {
140        self.cache.clear();
141    }
142
143    /// Get cache statistics.
144    pub fn cache_statistics(&self) -> cache::CacheStatistics {
145        self.cache.statistics()
146    }
147}
148
149impl Default for QueryEngine {
150    fn default() -> Self {
151        Self::new()
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_query_engine_creation() {
161        let engine = QueryEngine::new();
162        assert!(engine.cache_statistics().hits == 0);
163    }
164
165    #[test]
166    fn test_parse_simple_query() -> Result<()> {
167        let sql = "SELECT id, name FROM users";
168        let statement = parse_sql(sql)?;
169
170        match statement {
171            Statement::Select(select) => {
172                assert_eq!(select.projection.len(), 2);
173                assert!(select.from.is_some());
174            }
175        }
176
177        Ok(())
178    }
179
180    #[test]
181    fn test_optimizer() -> Result<()> {
182        let sql = "SELECT * FROM users WHERE 1 + 1 = 2";
183        let statement = parse_sql(sql)?;
184
185        let optimizer = Optimizer::new();
186        let optimized = optimizer.optimize(statement)?;
187
188        assert!(optimized.original_cost.total() >= 0.0);
189        assert!(optimized.optimized_cost.total() >= 0.0);
190
191        Ok(())
192    }
193}