#![warn(missing_docs)]
#![warn(clippy::all)]
#![allow(clippy::module_name_repetitions)]
pub mod cache;
pub mod error;
pub mod executor;
pub mod explain;
pub mod index;
pub mod optimizer;
pub mod parallel;
pub mod parser;
pub use cache::{CacheConfig, QueryCache};
pub use error::{QueryError, Result};
pub use executor::Executor;
pub use explain::ExplainPlan;
pub use optimizer::{OptimizedQuery, Optimizer, OptimizerConfig};
pub use parser::{Statement, parse_sql};
pub struct QueryEngine {
_parser_marker: std::marker::PhantomData<()>,
optimizer: Optimizer,
executor: Executor,
cache: QueryCache,
}
impl QueryEngine {
pub fn new() -> Self {
Self {
_parser_marker: std::marker::PhantomData,
optimizer: Optimizer::new(),
executor: Executor::new(),
cache: QueryCache::new(CacheConfig::default()),
}
}
pub fn with_config(optimizer_config: OptimizerConfig, cache_config: CacheConfig) -> Self {
Self {
_parser_marker: std::marker::PhantomData,
optimizer: Optimizer::with_config(optimizer_config),
executor: Executor::new(),
cache: QueryCache::new(cache_config),
}
}
pub fn optimizer(&self) -> &Optimizer {
&self.optimizer
}
pub fn executor(&mut self) -> &mut Executor {
&mut self.executor
}
pub fn cache(&self) -> &QueryCache {
&self.cache
}
pub async fn execute_sql(&mut self, sql: &str) -> Result<Vec<executor::scan::RecordBatch>> {
let statement = parse_sql(sql)?;
if let Some(cached) = self.cache.get(&statement) {
return Ok(cached);
}
let optimized = self.optimizer.optimize(statement.clone())?;
let results = self.executor.execute(&optimized.statement).await?;
self.cache.put(&statement, results.clone());
Ok(results)
}
pub fn explain_sql(&self, sql: &str) -> Result<ExplainPlan> {
let statement = parse_sql(sql)?;
let optimized = self.optimizer.optimize(statement)?;
Ok(ExplainPlan::from_optimized(&optimized))
}
pub fn register_data_source(
&mut self,
name: String,
source: std::sync::Arc<dyn executor::scan::DataSource>,
) {
self.executor.register_data_source(name, source);
}
pub fn clear_cache(&self) {
self.cache.clear();
}
pub fn cache_statistics(&self) -> cache::CacheStatistics {
self.cache.statistics()
}
}
impl Default for QueryEngine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_query_engine_creation() {
let engine = QueryEngine::new();
assert!(engine.cache_statistics().hits == 0);
}
#[test]
fn test_parse_simple_query() -> Result<()> {
let sql = "SELECT id, name FROM users";
let statement = parse_sql(sql)?;
match statement {
Statement::Select(select) => {
assert_eq!(select.projection.len(), 2);
assert!(select.from.is_some());
}
}
Ok(())
}
#[test]
fn test_optimizer() -> Result<()> {
let sql = "SELECT * FROM users WHERE 1 + 1 = 2";
let statement = parse_sql(sql)?;
let optimizer = Optimizer::new();
let optimized = optimizer.optimize(statement)?;
assert!(optimized.original_cost.total() >= 0.0);
assert!(optimized.optimized_cost.total() >= 0.0);
Ok(())
}
}