1use super::*;
2
3pub struct DataIndexingEngine {
5 indexes: HashMap<String, DataIndex>,
6 indexing_strategy: IndexingStrategy,
7 query_optimizer: QueryOptimizer,
8}
9
10#[derive(Debug, Clone)]
12pub struct DataIndex {
13 pub index_id: String,
14 pub index_type: IndexType,
15 pub indexed_columns: Vec<String>,
16 pub statistics: IndexStatistics,
17}
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub enum IndexType {
22 BTree,
23 Hash,
24 Bitmap,
25 FullText,
26 Spatial,
27 TimeSeries,
28}
29
30#[derive(Debug, Clone)]
32pub struct IndexStatistics {
33 pub entries: u64,
34 pub size: u64,
35 pub selectivity: f64,
36 pub usage_count: u64,
37}
38
39pub struct QueryOptimizer {
41 optimization_rules: Vec<OptimizationRule>,
42 cost_model: CostModel,
43 execution_plan: ExecutionPlan,
44}
45
46#[derive(Debug, Clone, PartialEq)]
48pub enum OptimizationRule {
49 PredicatePushdown,
50 IndexSelection,
51 JoinOrder,
52 AggregationPushdown,
53 Materialization,
54}
55
56pub struct CostModel {
58 pub cpu_cost: f64,
59 pub io_cost: f64,
60 pub memory_cost: f64,
61 pub network_cost: f64,
62}
63
64#[derive(Debug, Clone)]
66pub struct ExecutionPlan {
67 pub plan_id: String,
68 pub operations: Vec<QueryOperation>,
69 pub estimated_cost: f64,
70 pub execution_time: u64,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum JoinType {
76 NestedLoop,
77 HashJoin,
78}
79
80#[derive(Debug, Clone, PartialEq)]
83pub enum QueryOperation {
84 Scan {
86 table: String,
87 estimated_rows: usize,
88 },
89 Filter { predicate: String, selectivity: f64 },
91 Join {
94 left_cost: f64,
95 right_cost: f64,
96 join_type: JoinType,
97 },
98 Aggregate { group_by: Vec<String> },
100 Sort { columns: Vec<String> },
102 Limit { count: usize },
104 Project { columns: Vec<String> },
106}
107
108#[derive(Debug, Clone)]
111pub struct QueryStep {
112 pub operation: QueryOperation,
113 pub estimated_cost: f64,
114 pub estimated_rows: usize,
115}
116
117#[derive(Debug, Clone)]
119pub struct QueryPlan {
120 pub operations: Vec<QueryStep>,
121 pub estimated_cost: f64,
122 pub estimated_rows: usize,
123}
124
125impl QueryOperation {
126 fn data_size_hint(&self) -> f64 {
130 match self {
131 QueryOperation::Scan { estimated_rows, .. } => *estimated_rows as f64,
132 QueryOperation::Filter { selectivity, .. } => 100.0 / selectivity.max(0.01),
133 QueryOperation::Join {
134 left_cost,
135 right_cost,
136 ..
137 } => left_cost * right_cost,
138 QueryOperation::Aggregate { group_by } => group_by.len().max(1) as f64 * 100.0,
139 QueryOperation::Sort { columns } => columns.len().max(1) as f64 * 100.0,
140 QueryOperation::Limit { count } => *count as f64,
141 QueryOperation::Project { columns } => columns.len().max(1) as f64 * 100.0,
142 }
143 }
144
145 fn plan_priority(&self) -> u8 {
149 match self {
150 QueryOperation::Scan { .. } => 0,
151 QueryOperation::Project { .. } => 0,
152 QueryOperation::Filter { .. } => 1,
153 QueryOperation::Join { .. } => 2,
154 QueryOperation::Aggregate { .. } => 3,
155 QueryOperation::Sort { .. } => 4,
156 QueryOperation::Limit { .. } => 5,
157 }
158 }
159}
160
161impl DataIndexingEngine {
162 pub fn new() -> Self {
163 Self {
164 indexes: HashMap::new(),
165 indexing_strategy: IndexingStrategy::BTree,
166 query_optimizer: QueryOptimizer::new(),
167 }
168 }
169
170 pub fn initialize(&mut self) -> Result<(), StatisticalError> {
171 self.query_optimizer.initialize()?;
172 Ok(())
173 }
174
175 pub fn indexing_strategy(&self) -> &IndexingStrategy {
177 &self.indexing_strategy
178 }
179
180 pub fn set_indexing_strategy(&mut self, strategy: IndexingStrategy) {
182 self.indexing_strategy = strategy;
183 }
184
185 pub fn add_index(&mut self, index: DataIndex) {
187 self.indexes.insert(index.index_id.clone(), index);
188 }
189
190 pub fn get_index(&self, index_id: &str) -> Option<&DataIndex> {
192 self.indexes.get(index_id)
193 }
194
195 pub fn remove_index(&mut self, index_id: &str) -> Option<DataIndex> {
197 self.indexes.remove(index_id)
198 }
199
200 pub fn list_index_ids(&self) -> Vec<String> {
202 self.indexes.keys().cloned().collect()
203 }
204
205 pub fn index_count(&self) -> usize {
207 self.indexes.len()
208 }
209
210 pub fn query_optimizer(&self) -> &QueryOptimizer {
212 &self.query_optimizer
213 }
214
215 pub fn query_optimizer_mut(&mut self) -> &mut QueryOptimizer {
217 &mut self.query_optimizer
218 }
219}
220
221impl QueryOptimizer {
222 pub fn new() -> Self {
223 Self {
224 optimization_rules: vec![
225 OptimizationRule::PredicatePushdown,
226 OptimizationRule::IndexSelection,
227 ],
228 cost_model: CostModel {
229 cpu_cost: 0.0,
230 io_cost: 0.0,
231 memory_cost: 0.0,
232 network_cost: 0.0,
233 },
234 execution_plan: ExecutionPlan {
235 plan_id: "default".to_string(),
236 operations: Vec::new(),
237 estimated_cost: 0.0,
238 execution_time: 0,
239 },
240 }
241 }
242
243 pub fn initialize(&mut self) -> Result<(), StatisticalError> {
244 Ok(())
245 }
246
247 pub fn optimization_rules(&self) -> &[OptimizationRule] {
249 &self.optimization_rules
250 }
251
252 pub fn add_optimization_rule(&mut self, rule: OptimizationRule) {
254 if !self.optimization_rules.contains(&rule) {
255 self.optimization_rules.push(rule);
256 }
257 }
258
259 pub fn has_rule(&self, rule: &OptimizationRule) -> bool {
261 self.optimization_rules.contains(rule)
262 }
263
264 pub fn estimate_cost(&self, operation: &QueryOperation) -> CostModel {
269 let n = operation.data_size_hint();
270 match operation {
271 QueryOperation::Scan { .. } => CostModel {
273 cpu_cost: 0.1 * n,
274 io_cost: 1.0 * n,
275 memory_cost: 0.05 * n,
276 network_cost: 0.0,
277 },
278 QueryOperation::Filter { .. } => CostModel {
280 cpu_cost: 0.2 * n,
281 io_cost: 0.0,
282 memory_cost: 0.02 * n,
283 network_cost: 0.0,
284 },
285 QueryOperation::Project { .. } => CostModel {
287 cpu_cost: 0.1 * n,
288 io_cost: 0.0,
289 memory_cost: 0.02 * n,
290 network_cost: 0.0,
291 },
292 QueryOperation::Aggregate { .. } => CostModel {
294 cpu_cost: 0.5 * n,
295 io_cost: 0.1 * n,
296 memory_cost: 0.3 * n,
297 network_cost: 0.0,
298 },
299 QueryOperation::Join { .. } => CostModel {
301 cpu_cost: 1.0 * n,
302 io_cost: 0.5 * n,
303 memory_cost: 1.0 * n,
304 network_cost: 0.5 * n,
305 },
306 QueryOperation::Sort { .. } => CostModel {
308 cpu_cost: 0.6 * n,
309 io_cost: 0.2 * n,
310 memory_cost: 0.5 * n,
311 network_cost: 0.0,
312 },
313 QueryOperation::Limit { .. } => CostModel {
315 cpu_cost: 0.05 * n,
316 io_cost: 0.0,
317 memory_cost: 0.01 * n,
318 network_cost: 0.0,
319 },
320 }
321 }
322
323 pub fn optimize_with_cost(
328 &mut self,
329 operations: &[QueryOperation],
330 ) -> Result<ExecutionPlan, StatisticalError> {
331 let mut indexed: Vec<(usize, QueryOperation)> = operations
332 .iter()
333 .cloned()
334 .map(|op| op)
335 .enumerate()
336 .collect();
337 indexed.sort_by(|a, b| {
340 let ca = self.estimate_cost(&a.1).total_cost();
341 let cb = self.estimate_cost(&b.1).total_cost();
342 ca.partial_cmp(&cb).unwrap_or(std::cmp::Ordering::Equal)
343 });
344
345 let mut ordered: Vec<QueryOperation> = indexed.into_iter().map(|(_, op)| op).collect();
346 let total: f64 = ordered
347 .iter()
348 .map(|op| self.estimate_cost(op).total_cost())
349 .sum();
350
351 self.cost_model = ordered.iter().map(|op| self.estimate_cost(op)).fold(
354 CostModel {
355 cpu_cost: 0.0,
356 io_cost: 0.0,
357 memory_cost: 0.0,
358 network_cost: 0.0,
359 },
360 |acc, c| CostModel {
361 cpu_cost: acc.cpu_cost + c.cpu_cost,
362 io_cost: acc.io_cost + c.io_cost,
363 memory_cost: acc.memory_cost + c.memory_cost,
364 network_cost: acc.network_cost + c.network_cost,
365 },
366 );
367
368 let plan = ExecutionPlan {
369 plan_id: format!("plan_{}", self.next_plan_id()),
370 operations: std::mem::take(&mut ordered),
371 estimated_cost: total,
372 execution_time: 0,
373 };
374 self.execution_plan = plan.clone();
375 Ok(plan)
376 }
377
378 pub fn get_execution_plan(&self) -> Option<&ExecutionPlan> {
380 if self.execution_plan.operations.is_empty() {
381 None
382 } else {
383 Some(&self.execution_plan)
384 }
385 }
386
387 fn next_plan_id(&self) -> u64 {
389 self.execution_plan.operations.len() as u64 + 1
391 }
392
393 pub fn optimize(&self, operations: Vec<QueryOperation>) -> Result<QueryPlan, StatisticalError> {
407 if operations.is_empty() {
408 return Ok(QueryPlan {
409 operations: Vec::new(),
410 estimated_cost: 0.0,
411 estimated_rows: 0,
412 });
413 }
414
415 let mut reordered: Vec<QueryOperation> = operations;
419 reordered.sort_by_key(|op| op.plan_priority());
420
421 for op in reordered.iter_mut() {
423 if let QueryOperation::Join {
424 left_cost,
425 right_cost,
426 join_type,
427 } = op
428 {
429 if *left_cost >= 1000.0 && *right_cost >= 1000.0 {
430 *join_type = JoinType::HashJoin;
431 } else if *left_cost < 100.0 && *right_cost < 100.0 {
432 *join_type = JoinType::NestedLoop;
433 }
434 }
435 }
436
437 let mut steps: Vec<QueryStep> = Vec::with_capacity(reordered.len());
439 let mut current_rows: usize = 0;
440 let mut total_cost: f64 = 0.0;
441
442 for op in reordered {
443 let (cost, output_rows) = Self::estimate_step(&op, current_rows);
444 steps.push(QueryStep {
445 operation: op,
446 estimated_cost: cost,
447 estimated_rows: output_rows,
448 });
449 current_rows = output_rows;
450 total_cost += cost;
451 }
452
453 Ok(QueryPlan {
454 operations: steps,
455 estimated_cost: total_cost,
456 estimated_rows: current_rows,
457 })
458 }
459
460 fn estimate_step(op: &QueryOperation, input_rows: usize) -> (f64, usize) {
463 match op {
464 QueryOperation::Scan { estimated_rows, .. } => {
465 let cost = *estimated_rows as f64 * 0.01;
466 (cost, *estimated_rows)
467 }
468 QueryOperation::Filter { selectivity, .. } => {
469 let n = input_rows.max(1) as f64;
470 let cost = n * selectivity * 0.005;
471 let output = (n * selectivity).round() as usize;
472 (cost, output)
473 }
474 QueryOperation::Join {
475 left_cost,
476 right_cost,
477 ..
478 } => {
479 let n = left_cost * right_cost;
480 let cost = n * 0.001;
481 let output = n.round() as usize;
482 (cost, output)
483 }
484 QueryOperation::Aggregate { .. } => {
485 let n = input_rows.max(1) as f64;
486 let cost = n * 0.01;
487 (cost, input_rows)
488 }
489 QueryOperation::Sort { .. } => {
490 let n = input_rows.max(1) as f64;
491 let cost = n * 0.01;
492 (cost, input_rows)
493 }
494 QueryOperation::Limit { count } => {
495 let cost = *count as f64 * 0.001;
496 let output = (*count).min(input_rows.max(1));
497 (cost, output)
498 }
499 QueryOperation::Project { .. } => {
500 let n = input_rows.max(1) as f64;
501 let cost = n * 0.001;
502 (cost, input_rows)
503 }
504 }
505 }
506}
507
508impl CostModel {
509 pub fn total_cost(&self) -> f64 {
511 self.cpu_cost + self.io_cost + self.memory_cost + self.network_cost
512 }
513
514 pub fn is_better_than(&self, other: &CostModel) -> bool {
516 self.total_cost() < other.total_cost()
517 }
518}