Skip to main content

hematite/query/
optimizer.rs

1//! Query optimizer for improving query execution plans
2
3use crate::catalog::Schema;
4use crate::error::Result;
5use crate::query::plan::{IndexType, QueryPlan, SelectAnalysis};
6
7#[derive(Debug, Clone)]
8pub struct QueryOptimizer {
9    catalog: Schema,
10}
11
12impl QueryOptimizer {
13    pub fn new(catalog: Schema) -> Self {
14        Self { catalog }
15    }
16
17    pub fn optimize(&self, plan: QueryPlan) -> Result<QueryPlan> {
18        let mut optimized_plan = plan;
19
20        if let Some(analysis) = optimized_plan.select_analysis.clone() {
21            if !analysis.has_complex_source {
22                let _table = self
23                    .catalog
24                    .get_table_by_name(&analysis.table_name)
25                    .ok_or_else(|| {
26                        crate::error::HematiteError::ParseError(format!(
27                            "Table '{}' not found during optimization",
28                            analysis.table_name
29                        ))
30                    })?;
31            }
32
33            let optimizations = self.optimize_select(&analysis)?;
34            let reduction = optimizations.estimated_cost_reduction.clamp(0.0, 0.9);
35            optimized_plan.estimated_cost =
36                (optimized_plan.estimated_cost * (1.0 - reduction)).max(1.0);
37            optimized_plan.optimizations = Some(optimizations);
38        }
39
40        Ok(optimized_plan)
41    }
42
43    pub fn optimize_select(&self, analysis: &SelectAnalysis) -> Result<SelectOptimizations> {
44        let mut optimizations = SelectOptimizations::new();
45
46        // Analyze WHERE clause for optimization opportunities
47        self.optimize_where_clause(analysis, &mut optimizations)?;
48
49        // Analyze SELECT clause for optimization opportunities
50        self.optimize_select_clause(analysis, &mut optimizations)?;
51
52        // Suggest index usage
53        self.suggest_indexes(analysis, &mut optimizations)?;
54
55        Ok(optimizations)
56    }
57
58    fn optimize_where_clause(
59        &self,
60        analysis: &SelectAnalysis,
61        optimizations: &mut SelectOptimizations,
62    ) -> Result<()> {
63        // Check if we can use indexes for WHERE conditions
64        for index_usage in &analysis.usable_indexes {
65            if matches!(index_usage.index_type, IndexType::PrimaryKey)
66                || index_usage.selectivity <= 0.1
67            {
68                optimizations.recommend_index_scan(index_usage.column_id.clone());
69            }
70        }
71
72        Ok(())
73    }
74
75    fn optimize_select_clause(
76        &self,
77        analysis: &SelectAnalysis,
78        optimizations: &mut SelectOptimizations,
79    ) -> Result<()> {
80        // Check if we can use covering index
81        if analysis.accessed_columns.len() <= 3 {
82            // Small number of columns - might benefit from covering index
83            optimizations.recommend_covering_index();
84        }
85
86        Ok(())
87    }
88
89    fn suggest_indexes(
90        &self,
91        analysis: &SelectAnalysis,
92        optimizations: &mut SelectOptimizations,
93    ) -> Result<()> {
94        // Suggest indexes for frequently accessed columns
95        let mut column_access_counts = std::collections::HashMap::new();
96
97        for access in &analysis.accessed_columns {
98            *column_access_counts.entry(access.column_id).or_insert(0) += 1;
99        }
100
101        for (column_id, count) in column_access_counts {
102            if count > 10 {
103                // Frequently accessed column - suggest index
104                optimizations.suggest_index(column_id);
105            }
106        }
107
108        Ok(())
109    }
110}
111
112#[derive(Debug, Clone)]
113pub struct SelectOptimizations {
114    pub recommended_index_scans: Vec<crate::catalog::ColumnId>,
115    pub recommended_covering_index: bool,
116    pub suggested_indexes: Vec<crate::catalog::ColumnId>,
117    pub estimated_cost_reduction: f64,
118}
119
120impl SelectOptimizations {
121    pub fn new() -> Self {
122        Self {
123            recommended_index_scans: Vec::new(),
124            recommended_covering_index: false,
125            suggested_indexes: Vec::new(),
126            estimated_cost_reduction: 0.0,
127        }
128    }
129
130    pub fn recommend_index_scan(&mut self, column_id: crate::catalog::ColumnId) {
131        self.recommended_index_scans.push(column_id);
132        self.estimated_cost_reduction += 0.5; // Assume 50% cost reduction
133    }
134
135    pub fn recommend_covering_index(&mut self) {
136        self.recommended_covering_index = true;
137        self.estimated_cost_reduction += 0.2; // Assume 20% cost reduction
138    }
139
140    pub fn suggest_index(&mut self, column_id: crate::catalog::ColumnId) {
141        self.suggested_indexes.push(column_id);
142    }
143}