1use crate::ast::Expr;
8use crate::planner::{LogicalPlan, PhysicalPlan};
9use alloc::collections::BTreeMap;
10use core::hash::Hasher;
11
12#[derive(Default)]
15struct FnvHasher {
16 state: u64,
17}
18
19impl FnvHasher {
20 const FNV_OFFSET: u64 = 0xcbf29ce484222325;
21 const FNV_PRIME: u64 = 0x100000001b3;
22
23 fn new() -> Self {
24 Self {
25 state: Self::FNV_OFFSET,
26 }
27 }
28}
29
30impl Hasher for FnvHasher {
31 fn finish(&self) -> u64 {
32 self.state
33 }
34
35 fn write(&mut self, bytes: &[u8]) {
36 for byte in bytes {
37 self.state ^= *byte as u64;
38 self.state = self.state.wrapping_mul(Self::FNV_PRIME);
39 }
40 }
41}
42
43pub fn compute_plan_fingerprint(plan: &LogicalPlan) -> u64 {
46 let mut hasher = FnvHasher::new();
47 hash_logical_plan(plan, &mut hasher);
48 hasher.finish()
49}
50
51fn hash_logical_plan<H: Hasher>(plan: &LogicalPlan, hasher: &mut H) {
52 match plan {
53 LogicalPlan::Scan { table } => {
54 hasher.write(b"scan");
55 hasher.write(table.as_bytes());
56 }
57 LogicalPlan::IndexScan {
58 table,
59 index,
60 range_start,
61 range_end,
62 include_start,
63 include_end,
64 } => {
65 hasher.write(b"index_scan");
66 hasher.write(table.as_bytes());
67 hasher.write(index.as_bytes());
68 if let Some(v) = range_start {
69 hash_value(v, hasher);
70 }
71 if let Some(v) = range_end {
72 hash_value(v, hasher);
73 }
74 hasher.write(&[*include_start as u8, *include_end as u8]);
75 }
76 LogicalPlan::IndexGet { table, index, key } => {
77 hasher.write(b"index_get");
78 hasher.write(table.as_bytes());
79 hasher.write(index.as_bytes());
80 hash_value(key, hasher);
81 }
82 LogicalPlan::IndexInGet { table, index, keys } => {
83 hasher.write(b"index_in_get");
84 hasher.write(table.as_bytes());
85 hasher.write(index.as_bytes());
86 for key in keys {
87 hash_value(key, hasher);
88 }
89 }
90 LogicalPlan::GinIndexScan {
91 table,
92 index,
93 column,
94 column_index,
95 path,
96 value,
97 query_type,
98 } => {
99 hasher.write(b"gin_index_scan");
100 hasher.write(table.as_bytes());
101 hasher.write(index.as_bytes());
102 hasher.write(column.as_bytes());
103 hasher.write(&column_index.to_le_bytes());
104 hasher.write(path.as_bytes());
105 if let Some(v) = value {
106 hash_value(v, hasher);
107 }
108 hasher.write(query_type.as_bytes());
109 }
110 LogicalPlan::GinIndexScanMulti {
111 table,
112 index,
113 column,
114 pairs,
115 } => {
116 hasher.write(b"gin_index_scan_multi");
117 hasher.write(table.as_bytes());
118 hasher.write(index.as_bytes());
119 hasher.write(column.as_bytes());
120 for (path, value) in pairs {
121 hasher.write(path.as_bytes());
122 hash_value(value, hasher);
123 }
124 }
125 LogicalPlan::Filter { input, predicate } => {
126 hasher.write(b"filter");
127 hash_logical_plan(input, hasher);
128 hash_expr(predicate, hasher);
129 }
130 LogicalPlan::Project { input, columns } => {
131 hasher.write(b"project");
132 hash_logical_plan(input, hasher);
133 for col in columns {
134 hash_expr(col, hasher);
135 }
136 }
137 LogicalPlan::Join {
138 left,
139 right,
140 condition,
141 join_type,
142 } => {
143 hasher.write(b"join");
144 hash_logical_plan(left, hasher);
145 hash_logical_plan(right, hasher);
146 hash_expr(condition, hasher);
147 hasher.write(&[*join_type as u8]);
148 }
149 LogicalPlan::Aggregate {
150 input,
151 group_by,
152 aggregates,
153 } => {
154 hasher.write(b"aggregate");
155 hash_logical_plan(input, hasher);
156 for col in group_by {
157 hash_expr(col, hasher);
158 }
159 for (func, expr) in aggregates {
160 hasher.write(&[*func as u8]);
161 hash_expr(expr, hasher);
162 }
163 }
164 LogicalPlan::Sort { input, order_by } => {
165 hasher.write(b"sort");
166 hash_logical_plan(input, hasher);
167 for (expr, order) in order_by {
168 hash_expr(expr, hasher);
169 hasher.write(&[*order as u8]);
170 }
171 }
172 LogicalPlan::Limit {
173 input,
174 limit,
175 offset,
176 } => {
177 hasher.write(b"limit");
178 hash_logical_plan(input, hasher);
179 hasher.write(&limit.to_le_bytes());
180 hasher.write(&offset.to_le_bytes());
181 }
182 LogicalPlan::CrossProduct { left, right } => {
183 hasher.write(b"cross_product");
184 hash_logical_plan(left, hasher);
185 hash_logical_plan(right, hasher);
186 }
187 LogicalPlan::Union { left, right, .. } => {
188 hasher.write(b"union");
189 hash_logical_plan(left, hasher);
190 hash_logical_plan(right, hasher);
191 }
192 LogicalPlan::Empty => {
193 hasher.write(b"empty");
194 }
195 }
196}
197
198fn hash_expr<H: Hasher>(expr: &Expr, hasher: &mut H) {
199 match expr {
200 Expr::Column(col_ref) => {
201 hasher.write(b"col");
202 hasher.write(col_ref.table.as_bytes());
203 hasher.write(col_ref.column.as_bytes());
204 hasher.write(&col_ref.index.to_le_bytes());
205 }
206 Expr::Literal(v) => {
207 hasher.write(b"lit");
208 hash_value(v, hasher);
209 }
210 Expr::BinaryOp { left, op, right } => {
211 hasher.write(b"binop");
212 hasher.write(&[*op as u8]);
213 hash_expr(left, hasher);
214 hash_expr(right, hasher);
215 }
216 Expr::UnaryOp { op, expr } => {
217 hasher.write(b"unop");
218 hasher.write(&[*op as u8]);
219 hash_expr(expr, hasher);
220 }
221 Expr::Function { name, args } => {
222 hasher.write(b"func");
223 hasher.write(name.as_bytes());
224 for arg in args {
225 hash_expr(arg, hasher);
226 }
227 }
228 Expr::Aggregate {
229 func,
230 expr,
231 distinct,
232 } => {
233 hasher.write(b"agg");
234 hasher.write(&[*func as u8]);
235 if let Some(e) = expr {
236 hash_expr(e, hasher);
237 }
238 hasher.write(&[*distinct as u8]);
239 }
240 Expr::Between { expr, low, high } => {
241 hasher.write(b"between");
242 hash_expr(expr, hasher);
243 hash_expr(low, hasher);
244 hash_expr(high, hasher);
245 }
246 Expr::NotBetween { expr, low, high } => {
247 hasher.write(b"not_between");
248 hash_expr(expr, hasher);
249 hash_expr(low, hasher);
250 hash_expr(high, hasher);
251 }
252 Expr::In { expr, list } => {
253 hasher.write(b"in");
254 hash_expr(expr, hasher);
255 for item in list {
256 hash_expr(item, hasher);
257 }
258 }
259 Expr::NotIn { expr, list } => {
260 hasher.write(b"not_in");
261 hash_expr(expr, hasher);
262 for item in list {
263 hash_expr(item, hasher);
264 }
265 }
266 Expr::Like { expr, pattern } => {
267 hasher.write(b"like");
268 hash_expr(expr, hasher);
269 hasher.write(pattern.as_bytes());
270 }
271 Expr::NotLike { expr, pattern } => {
272 hasher.write(b"not_like");
273 hash_expr(expr, hasher);
274 hasher.write(pattern.as_bytes());
275 }
276 Expr::Match { expr, pattern } => {
277 hasher.write(b"match");
278 hash_expr(expr, hasher);
279 hasher.write(pattern.as_bytes());
280 }
281 Expr::NotMatch { expr, pattern } => {
282 hasher.write(b"not_match");
283 hash_expr(expr, hasher);
284 hasher.write(pattern.as_bytes());
285 }
286 }
287}
288
289fn hash_value<H: Hasher>(value: &cynos_core::Value, hasher: &mut H) {
290 use cynos_core::Value;
291 match value {
292 Value::Null => hasher.write(b"null"),
293 Value::Boolean(b) => {
294 hasher.write(b"bool");
295 hasher.write(&[*b as u8]);
296 }
297 Value::Int32(i) => {
298 hasher.write(b"i32");
299 hasher.write(&i.to_le_bytes());
300 }
301 Value::Int64(i) => {
302 hasher.write(b"i64");
303 hasher.write(&i.to_le_bytes());
304 }
305 Value::Float64(f) => {
306 hasher.write(b"f64");
307 hasher.write(&f.to_le_bytes());
308 }
309 Value::String(s) => {
310 hasher.write(b"str");
311 hasher.write(s.as_bytes());
312 }
313 Value::DateTime(dt) => {
314 hasher.write(b"dt");
315 hasher.write(&dt.to_le_bytes());
316 }
317 Value::Bytes(b) => {
318 hasher.write(b"bytes");
319 hasher.write(b);
320 }
321 Value::Jsonb(j) => {
322 hasher.write(b"jsonb");
323 use alloc::format;
325 let s = format!("{:?}", j);
326 hasher.write(s.as_bytes());
327 }
328 }
329}
330
331struct CacheEntry {
333 plan: PhysicalPlan,
334 last_access: u64,
335}
336
337pub struct PlanCache {
342 cache: BTreeMap<u64, CacheEntry>,
344 max_size: usize,
346 access_counter: u64,
348 hits: u64,
350 misses: u64,
351}
352
353impl PlanCache {
354 pub fn new(max_size: usize) -> Self {
356 Self {
357 cache: BTreeMap::new(),
358 max_size,
359 access_counter: 0,
360 hits: 0,
361 misses: 0,
362 }
363 }
364
365 pub fn default_size() -> Self {
367 Self::new(64)
368 }
369
370 pub fn get(&mut self, fingerprint: u64) -> Option<&PhysicalPlan> {
372 self.access_counter += 1;
373 if let Some(entry) = self.cache.get_mut(&fingerprint) {
374 entry.last_access = self.access_counter;
375 self.hits += 1;
376 Some(&entry.plan)
377 } else {
378 self.misses += 1;
379 None
380 }
381 }
382
383 pub fn insert(&mut self, fingerprint: u64, plan: PhysicalPlan) {
386 if self.cache.len() >= self.max_size {
388 self.evict_lru();
389 }
390
391 self.access_counter += 1;
392 self.cache.insert(
393 fingerprint,
394 CacheEntry {
395 plan,
396 last_access: self.access_counter,
397 },
398 );
399 }
400
401 pub fn get_or_insert_with<F>(&mut self, fingerprint: u64, compile: F) -> &PhysicalPlan
403 where
404 F: FnOnce() -> PhysicalPlan,
405 {
406 self.access_counter += 1;
407
408 if self.cache.contains_key(&fingerprint) {
409 let entry = self.cache.get_mut(&fingerprint).unwrap();
410 entry.last_access = self.access_counter;
411 self.hits += 1;
412 &self.cache.get(&fingerprint).unwrap().plan
413 } else {
414 self.misses += 1;
415
416 if self.cache.len() >= self.max_size {
418 self.evict_lru();
419 }
420
421 let plan = compile();
422 self.cache.insert(
423 fingerprint,
424 CacheEntry {
425 plan,
426 last_access: self.access_counter,
427 },
428 );
429 &self.cache.get(&fingerprint).unwrap().plan
430 }
431 }
432
433 fn evict_lru(&mut self) {
435 if self.cache.is_empty() {
436 return;
437 }
438
439 let lru_key = self
441 .cache
442 .iter()
443 .min_by_key(|(_, entry)| entry.last_access)
444 .map(|(k, _)| *k);
445
446 if let Some(key) = lru_key {
447 self.cache.remove(&key);
448 }
449 }
450
451 pub fn clear(&mut self) {
453 self.cache.clear();
454 self.hits = 0;
455 self.misses = 0;
456 }
457
458 pub fn len(&self) -> usize {
460 self.cache.len()
461 }
462
463 pub fn is_empty(&self) -> bool {
465 self.cache.is_empty()
466 }
467
468 pub fn hits(&self) -> u64 {
470 self.hits
471 }
472
473 pub fn misses(&self) -> u64 {
475 self.misses
476 }
477
478 pub fn hit_rate(&self) -> f64 {
480 let total = self.hits + self.misses;
481 if total == 0 {
482 0.0
483 } else {
484 self.hits as f64 / total as f64
485 }
486 }
487
488 pub fn invalidate_table(&mut self, _table: &str) {
491 self.cache.clear();
495 }
496}
497
498#[cfg(test)]
499mod tests {
500 use super::*;
501 use alloc::boxed::Box;
502 use alloc::string::String;
503
504 #[test]
505 fn test_plan_fingerprint_same_plan() {
506 let plan1 = LogicalPlan::Scan {
507 table: "users".into(),
508 };
509 let plan2 = LogicalPlan::Scan {
510 table: "users".into(),
511 };
512
513 assert_eq!(
514 compute_plan_fingerprint(&plan1),
515 compute_plan_fingerprint(&plan2)
516 );
517 }
518
519 #[test]
520 fn test_plan_fingerprint_different_plans() {
521 let plan1 = LogicalPlan::Scan {
522 table: "users".into(),
523 };
524 let plan2 = LogicalPlan::Scan {
525 table: "orders".into(),
526 };
527
528 assert_ne!(
529 compute_plan_fingerprint(&plan1),
530 compute_plan_fingerprint(&plan2)
531 );
532 }
533
534 #[test]
535 fn test_plan_fingerprint_with_filter() {
536 let plan1 = LogicalPlan::Filter {
537 input: Box::new(LogicalPlan::Scan {
538 table: "users".into(),
539 }),
540 predicate: Expr::eq(
541 Expr::column("users", "id", 0),
542 Expr::literal(cynos_core::Value::Int64(42)),
543 ),
544 };
545 let plan2 = LogicalPlan::Filter {
546 input: Box::new(LogicalPlan::Scan {
547 table: "users".into(),
548 }),
549 predicate: Expr::eq(
550 Expr::column("users", "id", 0),
551 Expr::literal(cynos_core::Value::Int64(42)),
552 ),
553 };
554
555 assert_eq!(
556 compute_plan_fingerprint(&plan1),
557 compute_plan_fingerprint(&plan2)
558 );
559 }
560
561 #[test]
562 fn test_plan_fingerprint_different_values() {
563 let plan1 = LogicalPlan::Filter {
564 input: Box::new(LogicalPlan::Scan {
565 table: "users".into(),
566 }),
567 predicate: Expr::eq(
568 Expr::column("users", "id", 0),
569 Expr::literal(cynos_core::Value::Int64(42)),
570 ),
571 };
572 let plan2 = LogicalPlan::Filter {
573 input: Box::new(LogicalPlan::Scan {
574 table: "users".into(),
575 }),
576 predicate: Expr::eq(
577 Expr::column("users", "id", 0),
578 Expr::literal(cynos_core::Value::Int64(43)),
579 ),
580 };
581
582 assert_ne!(
583 compute_plan_fingerprint(&plan1),
584 compute_plan_fingerprint(&plan2)
585 );
586 }
587
588 #[test]
589 fn test_cache_basic() {
590 let mut cache = PlanCache::new(10);
591
592 let table: String = "users".into();
593 let plan = PhysicalPlan::table_scan(table);
594 let fingerprint = 12345u64;
595
596 cache.insert(fingerprint, plan);
597
598 assert!(cache.get(fingerprint).is_some());
599 assert!(cache.get(99999).is_none());
600 }
601
602 #[test]
603 fn test_cache_lru_eviction() {
604 let mut cache = PlanCache::new(2);
605
606 let t1: String = "t1".into();
607 let t2: String = "t2".into();
608 let t3: String = "t3".into();
609
610 cache.insert(1, PhysicalPlan::table_scan(t1));
611 cache.insert(2, PhysicalPlan::table_scan(t2));
612
613 cache.get(1);
615
616 cache.insert(3, PhysicalPlan::table_scan(t3));
618
619 assert!(cache.get(1).is_some());
620 assert!(cache.get(2).is_none()); assert!(cache.get(3).is_some());
622 }
623
624 #[test]
625 fn test_cache_stats() {
626 let mut cache = PlanCache::new(10);
627
628 let t1: String = "t1".into();
629 cache.insert(1, PhysicalPlan::table_scan(t1));
630
631 cache.get(1); cache.get(1); cache.get(2); assert_eq!(cache.hits(), 2);
636 assert_eq!(cache.misses(), 1);
637 }
638
639 #[test]
640 fn test_cache_get_or_insert() {
641 let mut cache = PlanCache::new(10);
642
643 let fingerprint = 12345u64;
644 let mut compile_count = 0;
645
646 let _ = cache.get_or_insert_with(fingerprint, || {
648 compile_count += 1;
649 let table: String = "users".into();
650 PhysicalPlan::table_scan(table)
651 });
652
653 let _ = cache.get_or_insert_with(fingerprint, || {
655 compile_count += 1;
656 let table: String = "users".into();
657 PhysicalPlan::table_scan(table)
658 });
659
660 assert_eq!(compile_count, 1);
661 assert_eq!(cache.hits(), 1);
662 assert_eq!(cache.misses(), 1);
663 }
664}