hematite/query/
optimizer.rs1use 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 self.optimize_where_clause(analysis, &mut optimizations)?;
48
49 self.optimize_select_clause(analysis, &mut optimizations)?;
51
52 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 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 if analysis.accessed_columns.len() <= 3 {
82 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 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 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; }
134
135 pub fn recommend_covering_index(&mut self) {
136 self.recommended_covering_index = true;
137 self.estimated_cost_reduction += 0.2; }
139
140 pub fn suggest_index(&mut self, column_id: crate::catalog::ColumnId) {
141 self.suggested_indexes.push(column_id);
142 }
143}