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;
42// Parallel query execution is gated behind the default-on `parallel` feature
43// so wasm32 / single-threaded consumers can opt out of rayon.
44#[cfg(feature = "parallel")]
45pub mod parallel;
46pub mod parser;
47
48pub use cache::{CacheConfig, QueryCache};
49pub use error::{QueryError, Result};
50pub use executor::Executor;
51pub use explain::ExplainPlan;
52pub use optimizer::{OptimizedQuery, Optimizer, OptimizerConfig};
53pub use parser::{Statement, parse_sql};
54
55/// Query engine that combines all components.
56pub struct QueryEngine {
57    /// Parser (stateless, no storage needed).
58    _parser_marker: std::marker::PhantomData<()>,
59    /// Optimizer.
60    optimizer: Optimizer,
61    /// Executor.
62    executor: Executor,
63    /// Cache.
64    cache: QueryCache,
65}
66
67impl QueryEngine {
68    /// Create a new query engine with default configuration.
69    pub fn new() -> Self {
70        Self {
71            _parser_marker: std::marker::PhantomData,
72            optimizer: Optimizer::new(),
73            executor: Executor::new(),
74            cache: QueryCache::new(CacheConfig::default()),
75        }
76    }
77
78    /// Create a new query engine with custom configuration.
79    pub fn with_config(optimizer_config: OptimizerConfig, cache_config: CacheConfig) -> Self {
80        Self {
81            _parser_marker: std::marker::PhantomData,
82            optimizer: Optimizer::with_config(optimizer_config),
83            executor: Executor::new(),
84            cache: QueryCache::new(cache_config),
85        }
86    }
87
88    /// Get the optimizer.
89    pub fn optimizer(&self) -> &Optimizer {
90        &self.optimizer
91    }
92
93    /// Get the executor.
94    pub fn executor(&mut self) -> &mut Executor {
95        &mut self.executor
96    }
97
98    /// Get the cache.
99    pub fn cache(&self) -> &QueryCache {
100        &self.cache
101    }
102
103    /// Execute a SQL query.
104    pub async fn execute_sql(&mut self, sql: &str) -> Result<Vec<executor::scan::RecordBatch>> {
105        // Parse SQL
106        let statement = parse_sql(sql)?;
107
108        // Check cache
109        if let Some(cached) = self.cache.get(&statement) {
110            return Ok(cached);
111        }
112
113        // Optimize
114        let optimized = self.optimizer.optimize(statement.clone())?;
115
116        // Execute
117        let results = self.executor.execute(&optimized.statement).await?;
118
119        // Cache results
120        self.cache.put(&statement, results.clone());
121
122        Ok(results)
123    }
124
125    /// Explain a SQL query.
126    pub fn explain_sql(&self, sql: &str) -> Result<ExplainPlan> {
127        let statement = parse_sql(sql)?;
128        let optimized = self.optimizer.optimize(statement)?;
129        Ok(ExplainPlan::from_optimized(&optimized))
130    }
131
132    /// Register a data source.
133    pub fn register_data_source(
134        &mut self,
135        name: String,
136        source: std::sync::Arc<dyn executor::scan::DataSource>,
137    ) {
138        self.executor.register_data_source(name, source);
139    }
140
141    /// Clear the query cache.
142    pub fn clear_cache(&self) {
143        self.cache.clear();
144    }
145
146    /// Get cache statistics.
147    pub fn cache_statistics(&self) -> cache::CacheStatistics {
148        self.cache.statistics()
149    }
150}
151
152impl Default for QueryEngine {
153    fn default() -> Self {
154        Self::new()
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_query_engine_creation() {
164        let engine = QueryEngine::new();
165        assert!(engine.cache_statistics().hits == 0);
166    }
167
168    #[test]
169    fn test_parse_simple_query() -> Result<()> {
170        let sql = "SELECT id, name FROM users";
171        let statement = parse_sql(sql)?;
172
173        match statement {
174            Statement::Select(select) => {
175                assert_eq!(select.projection.len(), 2);
176                assert!(select.from.is_some());
177            }
178        }
179
180        Ok(())
181    }
182
183    #[test]
184    fn test_optimizer() -> Result<()> {
185        let sql = "SELECT * FROM users WHERE 1 + 1 = 2";
186        let statement = parse_sql(sql)?;
187
188        let optimizer = Optimizer::new();
189        let optimized = optimizer.optimize(statement)?;
190
191        assert!(optimized.original_cost.total() >= 0.0);
192        assert!(optimized.optimized_cost.total() >= 0.0);
193
194        Ok(())
195    }
196}