1use crate::ast::{Assignment, Expr, Literal};
29use crate::plan::PlanNode;
30use rustc_hash::FxHashMap;
31
32pub struct PlanCache {
38 cache: FxHashMap<u64, PlanNode>,
39 capacity: usize,
40 pub hits: u64,
41 pub misses: u64,
42}
43
44impl PlanCache {
45 pub fn new(capacity: usize) -> Self {
46 PlanCache {
47 cache: FxHashMap::default(),
48 capacity,
49 hits: 0,
50 misses: 0,
51 }
52 }
53
54 pub fn insert(&mut self, hash: u64, plan: PlanNode, source_literal_count: usize) {
72 if contains_grouped_having(&plan) {
80 return;
81 }
82 if nested_projection_defeats_cache(&plan) {
89 return;
90 }
91 if count_literal_slots(&plan) != source_literal_count {
92 return;
93 }
94 if self.cache.len() >= self.capacity && !self.cache.contains_key(&hash) {
95 self.cache.clear();
100 }
101 self.cache.insert(hash, plan);
102 }
103
104 pub fn get_with_substitution(&mut self, hash: u64, literals: &[Literal]) -> Option<PlanNode> {
116 match self.cache.get(&hash) {
117 Some(template) => {
118 self.hits += 1;
119 let mut plan = template.clone();
120 let mut idx = 0usize;
121 substitute_plan(&mut plan, literals, &mut idx);
122 debug_assert_eq!(
123 idx,
124 literals.len(),
125 "plan substitution consumed {idx} literals but query had {}",
126 literals.len(),
127 );
128 Some(plan)
129 }
130 None => {
131 self.misses += 1;
132 None
133 }
134 }
135 }
136
137 pub fn len(&self) -> usize {
138 self.cache.len()
139 }
140
141 pub fn is_empty(&self) -> bool {
142 self.cache.is_empty()
143 }
144
145 pub fn clear(&mut self) {
146 self.cache.clear();
147 }
148}
149
150pub(crate) fn nested_projection_defeats_cache(plan: &PlanNode) -> bool {
155 fn nested_defeats(nested: &crate::plan::NestedProjection) -> bool {
156 nested.offset_before_limit
157 || nested.fields.iter().any(|field| match field {
158 crate::plan::NestedField::Nested(inner) => nested_defeats(inner),
159 crate::plan::NestedField::Scalar { .. } => false,
160 })
161 }
162 fn walk(plan: &PlanNode) -> bool {
163 match plan {
164 PlanNode::NestedProject { input, fields } => {
165 walk(input)
166 || fields.iter().any(|field| match field {
167 crate::plan::NestedProjectField::Nested(nested) => nested_defeats(nested),
168 crate::plan::NestedProjectField::Plain(_) => false,
169 })
170 }
171 PlanNode::Filter { input, .. }
172 | PlanNode::Project { input, .. }
173 | PlanNode::Sort { input, .. }
174 | PlanNode::Limit { input, .. }
175 | PlanNode::Offset { input, .. }
176 | PlanNode::Aggregate { input, .. }
177 | PlanNode::Distinct { input }
178 | PlanNode::GroupBy { input, .. }
179 | PlanNode::Update { input, .. }
180 | PlanNode::Delete { input, .. }
181 | PlanNode::Window { input, .. }
182 | PlanNode::Explain { input } => walk(input),
183 PlanNode::NestedLoopJoin { left, right, .. } | PlanNode::Union { left, right, .. } => {
184 walk(left) || walk(right)
185 }
186 _ => false,
187 }
188 }
189 walk(plan)
190}
191
192fn substitute_nested_projection(
199 nested: &mut crate::plan::NestedProjection,
200 literals: &[Literal],
201 idx: &mut usize,
202) {
203 if let Some(residual) = &mut nested.residual {
204 substitute_expr(residual, literals, idx);
205 }
206 if let Some(limit) = &mut nested.limit {
207 substitute_expr(limit, literals, idx);
208 }
209 if let Some(offset) = &mut nested.offset {
210 substitute_expr(offset, literals, idx);
211 }
212 for field in &mut nested.fields {
215 if let crate::plan::NestedField::Nested(inner) = field {
216 substitute_nested_projection(inner, literals, idx);
217 }
218 }
219}
220
221fn count_nested_projection(nested: &crate::plan::NestedProjection, n: &mut usize) {
224 if let Some(residual) = &nested.residual {
225 count_expr(residual, n);
226 }
227 if let Some(limit) = &nested.limit {
228 count_expr(limit, n);
229 }
230 if let Some(offset) = &nested.offset {
231 count_expr(offset, n);
232 }
233 for field in &nested.fields {
234 if let crate::plan::NestedField::Nested(inner) = field {
235 count_nested_projection(inner, n);
236 }
237 }
238}
239
240fn contains_grouped_having(plan: &PlanNode) -> bool {
241 match plan {
242 PlanNode::GroupBy {
243 having: Some(_), ..
244 } => true,
245 PlanNode::Filter { input, .. }
246 | PlanNode::Project { input, .. }
247 | PlanNode::Sort { input, .. }
248 | PlanNode::Limit { input, .. }
249 | PlanNode::Offset { input, .. }
250 | PlanNode::Aggregate { input, .. }
251 | PlanNode::Distinct { input }
252 | PlanNode::GroupBy { input, .. }
253 | PlanNode::Update { input, .. }
254 | PlanNode::Delete { input, .. }
255 | PlanNode::Window { input, .. }
256 | PlanNode::NestedProject { input, .. }
257 | PlanNode::Explain { input } => contains_grouped_having(input),
258 PlanNode::NestedLoopJoin { left, right, .. } | PlanNode::Union { left, right, .. } => {
259 contains_grouped_having(left) || contains_grouped_having(right)
260 }
261 PlanNode::SeqScan { .. }
262 | PlanNode::AliasScan { .. }
263 | PlanNode::IndexScan { .. }
264 | PlanNode::RangeScan { .. }
265 | PlanNode::ExprIndexScan { .. }
266 | PlanNode::ExprRangeScan { .. }
267 | PlanNode::OrderedExprIndexScan { .. }
268 | PlanNode::AlterTable { .. }
269 | PlanNode::DropTable { .. }
270 | PlanNode::Insert { .. }
271 | PlanNode::Upsert { .. }
272 | PlanNode::CreateTable { .. }
273 | PlanNode::ListTypes
274 | PlanNode::Describe { .. }
275 | PlanNode::CreateView { .. }
276 | PlanNode::RefreshView { .. }
277 | PlanNode::DropView { .. }
278 | PlanNode::Begin
279 | PlanNode::Commit
280 | PlanNode::Rollback => false,
281 }
282}
283
284pub(crate) fn substitute_plan(plan: &mut PlanNode, literals: &[Literal], idx: &mut usize) {
303 match plan {
304 PlanNode::SeqScan { .. } => {}
305 PlanNode::AliasScan { .. } => {}
306 PlanNode::IndexScan { key, .. } => {
307 substitute_expr(key, literals, idx);
308 }
309 PlanNode::RangeScan { start, end, .. } => {
310 if let Some((expr, _)) = start {
311 substitute_expr(expr, literals, idx);
312 }
313 if let Some((expr, _)) = end {
314 substitute_expr(expr, literals, idx);
315 }
316 }
317 PlanNode::ExprIndexScan { key, .. } => substitute_expr(key, literals, idx),
318 PlanNode::ExprRangeScan { start, end, .. } => {
319 if let Some((expr, _)) = start {
320 substitute_expr(expr, literals, idx);
321 }
322 if let Some((expr, _)) = end {
323 substitute_expr(expr, literals, idx);
324 }
325 }
326 PlanNode::OrderedExprIndexScan { limit, offset, .. } => {
327 substitute_expr(limit, literals, idx);
328 if let Some(offset) = offset {
329 substitute_expr(offset, literals, idx);
330 }
331 }
332 PlanNode::Filter { input, predicate } => {
333 substitute_plan(input, literals, idx);
334 substitute_expr(predicate, literals, idx);
335 }
336 PlanNode::Project { input, fields } => {
337 if let PlanNode::GroupBy {
338 input: group_input,
339 keys,
340 aggregates,
341 having: None,
342 } = input.as_mut()
343 {
344 substitute_plan(group_input, literals, idx);
349 for key in keys {
350 substitute_expr(&mut key.expr, literals, idx);
351 }
352 let mut visited = std::collections::HashSet::new();
353 for field in fields {
354 substitute_group_projection_expr(
355 &mut field.expr,
356 aggregates,
357 &mut visited,
358 literals,
359 idx,
360 );
361 }
362 } else {
363 substitute_plan(input, literals, idx);
364 for f in fields {
365 substitute_expr(&mut f.expr, literals, idx);
366 }
367 }
368 }
369 PlanNode::NestedProject { input, fields } => {
370 substitute_plan(input, literals, idx);
376 for field in fields {
377 match field {
378 crate::plan::NestedProjectField::Plain(f) => {
379 substitute_expr(&mut f.expr, literals, idx);
380 }
381 crate::plan::NestedProjectField::Nested(nested) => {
382 substitute_nested_projection(nested, literals, idx);
383 }
384 }
385 }
386 }
387 PlanNode::Sort { input, keys } => {
388 substitute_plan(input, literals, idx);
389 for key in keys {
390 substitute_expr(&mut key.expr, literals, idx);
391 }
392 }
393 PlanNode::AlterTable { .. } => {}
394 PlanNode::DropTable { .. } => {}
395 PlanNode::Limit { input, count } => {
396 if let PlanNode::Offset {
405 input: inner,
406 count: off_count,
407 } = input.as_mut()
408 {
409 substitute_plan(inner, literals, idx);
410 substitute_expr(count, literals, idx);
411 substitute_expr(off_count, literals, idx);
412 } else {
413 substitute_plan(input, literals, idx);
414 substitute_expr(count, literals, idx);
415 }
416 }
417 PlanNode::Offset { input, count } => {
418 substitute_plan(input, literals, idx);
421 substitute_expr(count, literals, idx);
422 }
423 PlanNode::Aggregate {
424 input, argument, ..
425 } => {
426 substitute_plan(input, literals, idx);
427 if let Some(argument) = argument {
428 substitute_expr(argument, literals, idx);
429 }
430 }
431 PlanNode::NestedLoopJoin {
432 left, right, on, ..
433 } => {
434 substitute_plan(left, literals, idx);
439 substitute_plan(right, literals, idx);
440 if let Some(pred) = on {
441 substitute_expr(pred, literals, idx);
442 }
443 }
444 PlanNode::Distinct { input } => {
445 substitute_plan(input, literals, idx);
446 }
447 PlanNode::GroupBy {
448 input,
449 keys,
450 aggregates,
451 having,
452 } => {
453 substitute_plan(input, literals, idx);
454 for key in keys {
455 substitute_expr(&mut key.expr, literals, idx);
456 }
457 for aggregate in aggregates {
458 substitute_expr(&mut aggregate.argument, literals, idx);
459 }
460 if let Some(pred) = having {
461 substitute_expr(pred, literals, idx);
462 }
463 }
464 PlanNode::Insert { rows, .. } => {
465 for assignments in rows {
466 substitute_assignments(assignments, literals, idx);
467 }
468 }
469 PlanNode::Upsert {
470 assignments,
471 on_conflict,
472 ..
473 } => {
474 substitute_assignments(assignments, literals, idx);
475 substitute_assignments(on_conflict, literals, idx);
476 }
477 PlanNode::Update {
478 input, assignments, ..
479 } => {
480 substitute_plan(input, literals, idx);
481 substitute_assignments(assignments, literals, idx);
482 }
483 PlanNode::Delete { input, .. } => {
484 substitute_plan(input, literals, idx);
485 }
486 PlanNode::CreateTable { .. } => {}
487 PlanNode::CreateView { .. } => {}
488 PlanNode::RefreshView { .. } => {}
489 PlanNode::DropView { .. } => {}
490 PlanNode::Window { input, windows } => {
491 substitute_plan(input, literals, idx);
492 for w in windows {
493 for arg in &mut w.args {
494 substitute_expr(arg, literals, idx);
495 }
496 for expr in &mut w.partition_by {
497 substitute_expr(expr, literals, idx);
498 }
499 for key in &mut w.order_by {
500 substitute_expr(&mut key.expr, literals, idx);
501 }
502 }
503 }
504 PlanNode::Union { left, right, .. } => {
505 substitute_plan(left, literals, idx);
506 substitute_plan(right, literals, idx);
507 }
508 PlanNode::Explain { input } => {
509 substitute_plan(input, literals, idx);
510 }
511 PlanNode::ListTypes | PlanNode::Describe { .. } => {}
512 PlanNode::Begin | PlanNode::Commit | PlanNode::Rollback => {}
513 }
514}
515
516fn substitute_assignments(assignments: &mut [Assignment], literals: &[Literal], idx: &mut usize) {
517 for a in assignments {
518 substitute_expr(&mut a.value, literals, idx);
519 }
520}
521
522fn substitute_group_projection_expr(
523 expr: &mut Expr,
524 aggregates: &mut [crate::plan::GroupAgg],
525 visited: &mut std::collections::HashSet<String>,
526 literals: &[Literal],
527 idx: &mut usize,
528) {
529 if let Expr::Field(name) = expr {
530 if visited.insert(name.clone()) {
531 if let Some(aggregate) = aggregates.iter_mut().find(|agg| agg.output_name == *name) {
532 substitute_expr(&mut aggregate.argument, literals, idx);
533 return;
534 }
535 }
536 }
537 match expr {
538 Expr::BinaryOp(left, _, right) | Expr::Coalesce(left, right) => {
539 substitute_group_projection_expr(left, aggregates, visited, literals, idx);
540 substitute_group_projection_expr(right, aggregates, visited, literals, idx);
541 }
542 Expr::UnaryOp(_, inner) | Expr::Cast(inner, _) => {
543 substitute_group_projection_expr(inner, aggregates, visited, literals, idx);
544 }
545 Expr::ScalarFunc(_, args) => {
546 for arg in args {
547 substitute_group_projection_expr(arg, aggregates, visited, literals, idx);
548 }
549 }
550 Expr::InList { expr, list, .. } => {
551 substitute_group_projection_expr(expr, aggregates, visited, literals, idx);
552 for item in list {
553 substitute_group_projection_expr(item, aggregates, visited, literals, idx);
554 }
555 }
556 Expr::Case { whens, else_expr } => {
557 for (condition, result) in whens {
558 substitute_group_projection_expr(condition, aggregates, visited, literals, idx);
559 substitute_group_projection_expr(result, aggregates, visited, literals, idx);
560 }
561 if let Some(expr) = else_expr {
562 substitute_group_projection_expr(expr, aggregates, visited, literals, idx);
563 }
564 }
565 _ => substitute_expr(expr, literals, idx),
566 }
567}
568
569pub(crate) fn count_literal_slots(plan: &PlanNode) -> usize {
575 let mut n = 0usize;
576 count_plan(plan, &mut n);
577 n
578}
579
580fn count_plan(plan: &PlanNode, n: &mut usize) {
581 match plan {
582 PlanNode::SeqScan { .. } => {}
583 PlanNode::AliasScan { .. } => {}
584 PlanNode::IndexScan { key, .. } => count_expr(key, n),
585 PlanNode::RangeScan { start, end, .. } => {
586 if let Some((expr, _)) = start {
587 count_expr(expr, n);
588 }
589 if let Some((expr, _)) = end {
590 count_expr(expr, n);
591 }
592 }
593 PlanNode::ExprIndexScan { key, .. } => count_expr(key, n),
594 PlanNode::ExprRangeScan { start, end, .. } => {
595 if let Some((expr, _)) = start {
596 count_expr(expr, n);
597 }
598 if let Some((expr, _)) = end {
599 count_expr(expr, n);
600 }
601 }
602 PlanNode::OrderedExprIndexScan { limit, offset, .. } => {
603 count_expr(limit, n);
604 if let Some(offset) = offset {
605 count_expr(offset, n);
606 }
607 }
608 PlanNode::Filter { input, predicate } => {
609 count_plan(input, n);
610 count_expr(predicate, n);
611 }
612 PlanNode::Project { input, fields } => {
613 if let PlanNode::GroupBy {
614 input: group_input,
615 keys,
616 aggregates,
617 having: None,
618 } = input.as_ref()
619 {
620 count_plan(group_input, n);
621 for key in keys {
622 count_expr(&key.expr, n);
623 }
624 let mut visited = std::collections::HashSet::new();
625 for field in fields {
626 count_group_projection_expr(&field.expr, aggregates, &mut visited, n);
627 }
628 } else {
629 count_plan(input, n);
630 for f in fields {
631 count_expr(&f.expr, n);
632 }
633 }
634 }
635 PlanNode::NestedProject { input, fields } => {
636 count_plan(input, n);
639 for field in fields {
640 match field {
641 crate::plan::NestedProjectField::Plain(f) => count_expr(&f.expr, n),
642 crate::plan::NestedProjectField::Nested(nested) => {
643 count_nested_projection(nested, n);
644 }
645 }
646 }
647 }
648 PlanNode::Sort { input, keys } => {
649 count_plan(input, n);
650 for key in keys {
651 count_expr(&key.expr, n);
652 }
653 }
654 PlanNode::Limit { input, count } => {
655 if let PlanNode::Offset {
660 input: inner,
661 count: off_count,
662 } = input.as_ref()
663 {
664 count_plan(inner, n);
665 count_expr(count, n);
666 count_expr(off_count, n);
667 } else {
668 count_plan(input, n);
669 count_expr(count, n);
670 }
671 }
672 PlanNode::Offset { input, count } => {
673 count_plan(input, n);
674 count_expr(count, n);
675 }
676 PlanNode::Aggregate {
677 input, argument, ..
678 } => {
679 count_plan(input, n);
680 if let Some(argument) = argument {
681 count_expr(argument, n);
682 }
683 }
684 PlanNode::NestedLoopJoin {
685 left, right, on, ..
686 } => {
687 count_plan(left, n);
688 count_plan(right, n);
689 if let Some(pred) = on {
690 count_expr(pred, n);
691 }
692 }
693 PlanNode::Distinct { input } => count_plan(input, n),
694 PlanNode::GroupBy {
695 input,
696 keys,
697 aggregates,
698 having,
699 } => {
700 count_plan(input, n);
701 for key in keys {
702 count_expr(&key.expr, n);
703 }
704 for aggregate in aggregates {
705 count_expr(&aggregate.argument, n);
706 }
707 if let Some(pred) = having {
708 count_expr(pred, n);
709 }
710 }
711 PlanNode::Insert { rows, .. } => {
712 for assignments in rows {
713 for a in assignments {
714 count_expr(&a.value, n);
715 }
716 }
717 }
718 PlanNode::Upsert {
719 assignments,
720 on_conflict,
721 ..
722 } => {
723 for a in assignments {
724 count_expr(&a.value, n);
725 }
726 for a in on_conflict {
727 count_expr(&a.value, n);
728 }
729 }
730 PlanNode::Update {
731 input, assignments, ..
732 } => {
733 count_plan(input, n);
734 for a in assignments {
735 count_expr(&a.value, n);
736 }
737 }
738 PlanNode::Delete { input, .. } => count_plan(input, n),
739 PlanNode::CreateTable { .. } => {}
740 PlanNode::AlterTable { .. } => {}
741 PlanNode::DropTable { .. } => {}
742 PlanNode::CreateView { .. } => {}
743 PlanNode::RefreshView { .. } => {}
744 PlanNode::DropView { .. } => {}
745 PlanNode::Window { input, windows } => {
746 count_plan(input, n);
747 for w in windows {
748 for arg in &w.args {
749 count_expr(arg, n);
750 }
751 for expr in &w.partition_by {
752 count_expr(expr, n);
753 }
754 for key in &w.order_by {
755 count_expr(&key.expr, n);
756 }
757 }
758 }
759 PlanNode::Union { left, right, .. } => {
760 count_plan(left, n);
761 count_plan(right, n);
762 }
763 PlanNode::Explain { input } => {
764 count_plan(input, n);
765 }
766 PlanNode::ListTypes | PlanNode::Describe { .. } => {}
767 PlanNode::Begin | PlanNode::Commit | PlanNode::Rollback => {}
768 }
769}
770
771fn count_group_projection_expr(
772 expr: &Expr,
773 aggregates: &[crate::plan::GroupAgg],
774 visited: &mut std::collections::HashSet<String>,
775 n: &mut usize,
776) {
777 if let Expr::Field(name) = expr {
778 if visited.insert(name.clone()) {
779 if let Some(aggregate) = aggregates.iter().find(|agg| agg.output_name == *name) {
780 count_expr(&aggregate.argument, n);
781 return;
782 }
783 }
784 }
785 match expr {
786 Expr::BinaryOp(left, _, right) | Expr::Coalesce(left, right) => {
787 count_group_projection_expr(left, aggregates, visited, n);
788 count_group_projection_expr(right, aggregates, visited, n);
789 }
790 Expr::UnaryOp(_, inner) | Expr::Cast(inner, _) => {
791 count_group_projection_expr(inner, aggregates, visited, n);
792 }
793 Expr::ScalarFunc(_, args) => {
794 for arg in args {
795 count_group_projection_expr(arg, aggregates, visited, n);
796 }
797 }
798 Expr::InList { expr, list, .. } => {
799 count_group_projection_expr(expr, aggregates, visited, n);
800 for item in list {
801 count_group_projection_expr(item, aggregates, visited, n);
802 }
803 }
804 Expr::Case { whens, else_expr } => {
805 for (condition, result) in whens {
806 count_group_projection_expr(condition, aggregates, visited, n);
807 count_group_projection_expr(result, aggregates, visited, n);
808 }
809 if let Some(expr) = else_expr {
810 count_group_projection_expr(expr, aggregates, visited, n);
811 }
812 }
813 _ => count_expr(expr, n),
814 }
815}
816
817fn count_expr(expr: &Expr, n: &mut usize) {
818 match expr {
819 Expr::Literal(_) => *n += 1,
820 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::Param(_) => {}
821 Expr::BinaryOp(l, _, r) => {
822 count_expr(l, n);
823 count_expr(r, n);
824 }
825 Expr::UnaryOp(_, inner) => count_expr(inner, n),
826 Expr::FunctionCall(_, inner, _) => count_expr(inner, n),
827 Expr::Coalesce(l, r) => {
828 count_expr(l, n);
829 count_expr(r, n);
830 }
831 Expr::InList { expr, list, .. } => {
832 count_expr(expr, n);
833 for item in list {
834 count_expr(item, n);
835 }
836 }
837 Expr::ScalarFunc(_, args) => {
838 for a in args {
839 count_expr(a, n);
840 }
841 }
842 Expr::Cast(inner, _) => count_expr(inner, n),
843 Expr::Case { whens, else_expr } => {
844 for (cond, result) in whens {
845 count_expr(cond, n);
846 count_expr(result, n);
847 }
848 if let Some(e) = else_expr {
849 count_expr(e, n);
850 }
851 }
852 Expr::InSubquery { expr, .. } => {
853 count_expr(expr, n);
854 }
857 Expr::ExistsSubquery { .. } => {
858 }
861 Expr::Window {
862 args,
863 partition_by,
864 order_by,
865 ..
866 } => {
867 for a in args {
868 count_expr(a, n);
869 }
870 for expr in partition_by {
871 count_expr(expr, n);
872 }
873 for key in order_by {
874 count_expr(&key.expr, n);
875 }
876 }
877 Expr::JsonPath { base, .. } => count_expr(base, n),
881 Expr::ValueLit(_) => {}
884 Expr::Null => {}
885 Expr::NestedQuery(_) => {}
889 }
890}
891
892fn substitute_expr(expr: &mut Expr, literals: &[Literal], idx: &mut usize) {
893 match expr {
894 Expr::Literal(_) => {
895 *expr = Expr::Literal(literals[*idx].clone());
899 *idx += 1;
900 }
901 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::Param(_) => {}
902 Expr::BinaryOp(l, _, r) => {
903 substitute_expr(l, literals, idx);
904 substitute_expr(r, literals, idx);
905 }
906 Expr::UnaryOp(_, inner) => {
907 substitute_expr(inner, literals, idx);
908 }
909 Expr::FunctionCall(_, inner, _) => {
910 substitute_expr(inner, literals, idx);
911 }
912 Expr::Coalesce(l, r) => {
913 substitute_expr(l, literals, idx);
914 substitute_expr(r, literals, idx);
915 }
916 Expr::InList { expr, list, .. } => {
917 substitute_expr(expr, literals, idx);
918 for item in list {
919 substitute_expr(item, literals, idx);
920 }
921 }
922 Expr::ScalarFunc(_, args) => {
923 for a in args {
924 substitute_expr(a, literals, idx);
925 }
926 }
927 Expr::Cast(inner, _) => substitute_expr(inner, literals, idx),
928 Expr::Case { whens, else_expr } => {
929 for (cond, result) in whens {
930 substitute_expr(cond, literals, idx);
931 substitute_expr(result, literals, idx);
932 }
933 if let Some(e) = else_expr {
934 substitute_expr(e, literals, idx);
935 }
936 }
937 Expr::InSubquery { expr, .. } => {
938 substitute_expr(expr, literals, idx);
939 }
940 Expr::ExistsSubquery { .. } => {
941 }
944 Expr::Window {
945 args,
946 partition_by,
947 order_by,
948 ..
949 } => {
950 for a in args {
951 substitute_expr(a, literals, idx);
952 }
953 for expr in partition_by {
954 substitute_expr(expr, literals, idx);
955 }
956 for key in order_by {
957 substitute_expr(&mut key.expr, literals, idx);
958 }
959 }
960 Expr::JsonPath { base, .. } => substitute_expr(base, literals, idx),
963 Expr::ValueLit(_) => {}
966 Expr::Null => {}
967 Expr::NestedQuery(_) => {}
970 }
971}
972
973#[cfg(test)]
974mod tests {
975 use super::*;
976 use crate::canonicalize::canonicalize;
977 use crate::planner;
978
979 #[test]
980 fn test_cache_hit_substitutes_literal() {
981 let mut cache = PlanCache::new(100);
982
983 let q1 = "User filter .id = 42";
985 let (h1, lits1) = canonicalize(q1).unwrap();
986 let p1 = planner::plan(q1).unwrap();
987 cache.insert(h1, p1, lits1.len());
988
989 let q2 = "User filter .id = 99";
992 let (h2, lits2) = canonicalize(q2).unwrap();
993 assert_eq!(h1, h2, "different literals must hash the same");
994
995 let plan = cache.get_with_substitution(h2, &lits2).expect("hit");
996
997 match plan {
999 PlanNode::IndexScan { key, .. } => {
1000 assert_eq!(key, Expr::Literal(Literal::Int(99)));
1001 }
1002 other => panic!("expected IndexScan, got {other:?}"),
1003 }
1004
1005 assert_eq!(lits1, vec![Literal::Int(42)]);
1008 assert_eq!(cache.hits, 1);
1009 assert_eq!(cache.misses, 0);
1010 }
1011
1012 #[test]
1013 fn test_subquery_plan_not_cached() {
1014 let mut cache = PlanCache::new(100);
1021 let q = "User filter .id in (Ord filter .total > 100 { .user_id })";
1022 let (h, lits) = canonicalize(q).unwrap();
1023 assert_eq!(lits.len(), 1, "canonicalize collects the inner literal");
1024 let plan = planner::plan(q).unwrap();
1025 assert_eq!(
1026 count_literal_slots(&plan),
1027 0,
1028 "the subquery literal is not a reachable substitution slot"
1029 );
1030 cache.insert(h, plan, lits.len());
1031 assert!(cache.is_empty(), "subquery plans must not be cached (#137)");
1032 assert!(cache.get_with_substitution(h, &lits).is_none());
1033 }
1034
1035 #[test]
1036 fn test_cache_miss_returns_none_and_bumps_counter() {
1037 let mut cache = PlanCache::new(100);
1038 assert!(cache.get_with_substitution(99999, &[]).is_none());
1039 assert_eq!(cache.misses, 1);
1040 assert_eq!(cache.hits, 0);
1041 }
1042
1043 #[test]
1044 fn test_multi_literal_filter_substitution() {
1045 let mut cache = PlanCache::new(100);
1046 let q1 = r#"User filter .age > 30 and .status = "active" { .name }"#;
1047 let (h1, lits1) = canonicalize(q1).unwrap();
1048 cache.insert(h1, planner::plan(q1).unwrap(), lits1.len());
1049
1050 let q2 = r#"User filter .age > 50 and .status = "pending" { .name }"#;
1051 let (h2, lits2) = canonicalize(q2).unwrap();
1052 let plan = cache.get_with_substitution(h2, &lits2).expect("hit");
1053
1054 let mut found = Vec::new();
1056 collect_literals_for_test(&plan, &mut found);
1057 assert_eq!(
1058 found,
1059 vec![Literal::Int(50), Literal::String("pending".into()),]
1060 );
1061 }
1062
1063 #[test]
1064 fn test_grouped_join_having_is_not_cached_without_source_slot_ordinals() {
1065 let mut cache = PlanCache::new(100);
1070 let q1 = "User as u join Order as o on u.id = o.user_id \
1071 group u.status having count(o.total) > 1 { u.status, n: count(o.total) }";
1072 let (h1, lits1) = canonicalize(q1).unwrap();
1073 let p1 = planner::plan(q1).unwrap();
1074
1075 assert_eq!(lits1.len(), 1, "only the HAVING literal is collected");
1079 assert_eq!(
1080 count_literal_slots(&p1),
1081 lits1.len(),
1082 "group keys/args are structural, so slots == literals (#137)"
1083 );
1084 cache.insert(h1, p1, lits1.len());
1085 assert!(cache.is_empty(), "grouped HAVING plans must not cache");
1086
1087 let q2 = "User as u join Order as o on u.id = o.user_id \
1088 group u.status having count(o.total) > 5 { u.status, n: count(o.total) }";
1089 let (h2, lits2) = canonicalize(q2).unwrap();
1090 assert_eq!(h1, h2, "different HAVING literal must hash the same");
1091
1092 assert!(cache.get_with_substitution(h2, &lits2).is_none());
1093 assert_eq!(cache.hits, 0);
1094 assert_eq!(cache.misses, 1);
1095 }
1096
1097 #[test]
1098 fn json_path_slot_count_invariant() {
1099 for q in [
1104 r#"Post filter .data->author->name = "x""#,
1105 r#"Post filter .data->tags->0 = "rust""#,
1106 r#"Post filter .data->age > 21 and .data->year = 2026"#,
1107 r#"Post filter .data->"weird key" = 1 { .id }"#,
1108 r#"Post { author: .data->author, first_tag: .data->tags->0 }"#,
1109 ] {
1110 let (_, lits) = canonicalize(q).unwrap();
1111 let plan = planner::plan(q).unwrap();
1112 assert_eq!(
1113 count_literal_slots(&plan),
1114 lits.len(),
1115 "slot count must equal source literal count for `{q}`"
1116 );
1117 }
1118 }
1119
1120 #[test]
1121 fn json_path_plan_round_trips_cache() {
1122 let mut cache = PlanCache::new(100);
1126 let q1 = r#"Post filter .data->age > 21"#;
1127 let (h1, lits1) = canonicalize(q1).unwrap();
1128 let p1 = planner::plan(q1).unwrap();
1129 assert_eq!(count_literal_slots(&p1), lits1.len());
1130 cache.insert(h1, p1, lits1.len());
1131 assert_eq!(cache.len(), 1, "path plan must cache");
1132
1133 let q2 = r#"Post filter .data->age > 65"#;
1134 let (h2, lits2) = canonicalize(q2).unwrap();
1135 assert_eq!(h1, h2, "same path, different literal → same hash");
1136 let plan = cache.get_with_substitution(h2, &lits2).expect("hit");
1137
1138 let mut found = Vec::new();
1139 collect_literals_for_test(&plan, &mut found);
1140 assert_eq!(found, vec![Literal::Int(65)], "new literal substituted");
1141 assert_eq!(cache.hits, 1);
1142 }
1143
1144 #[test]
1145 fn json_path_different_path_is_a_cache_miss() {
1146 let mut cache = PlanCache::new(100);
1147 let q1 = r#"Post filter .data->age > 21"#;
1148 let (h1, lits1) = canonicalize(q1).unwrap();
1149 cache.insert(h1, planner::plan(q1).unwrap(), lits1.len());
1150
1151 let q2 = r#"Post filter .data->year > 21"#;
1154 let (h2, lits2) = canonicalize(q2).unwrap();
1155 assert!(
1156 cache.get_with_substitution(h2, &lits2).is_none(),
1157 "a different path must not hit the cached plan"
1158 );
1159 }
1160
1161 #[test]
1162 fn expression_aggregate_literal_substitutes_on_cache_hit() {
1163 let mut cache = PlanCache::new(8);
1164 let q1 = "Post group .data->kind { total: sum(.data->age + 1) }";
1165 let (h1, literals1) = canonicalize(q1).unwrap();
1166 let plan1 = planner::plan(q1).unwrap();
1167 assert_eq!(count_literal_slots(&plan1), literals1.len());
1168 cache.insert(h1, plan1, literals1.len());
1169
1170 let q2 = "Post group .data->kind { total: sum(.data->age + 7) }";
1171 let (h2, literals2) = canonicalize(q2).unwrap();
1172 assert_eq!(h1, h2);
1173 let plan = cache.get_with_substitution(h2, &literals2).expect("hit");
1174 let mut found = Vec::new();
1175 collect_literals_for_test(&plan, &mut found);
1176 assert_eq!(found, vec![Literal::Int(7)]);
1177 }
1178
1179 #[test]
1180 fn expression_index_equality_and_range_bounds_substitute_on_cache_hits() {
1181 let mut cache = PlanCache::new(8);
1182
1183 let q1 = "Post filter .data->age = 21";
1184 let (h1, literals1) = canonicalize(q1).unwrap();
1185 let plan1 = planner::plan(q1).unwrap();
1186 assert!(matches!(plan1, PlanNode::ExprIndexScan { .. }));
1187 assert_eq!(count_literal_slots(&plan1), literals1.len());
1188 cache.insert(h1, plan1, literals1.len());
1189
1190 let q2 = "Post filter .data->age = 65";
1191 let (h2, literals2) = canonicalize(q2).unwrap();
1192 assert_eq!(h1, h2);
1193 let plan = cache.get_with_substitution(h2, &literals2).expect("hit");
1194 let mut found = Vec::new();
1195 collect_literals_for_test(&plan, &mut found);
1196 assert_eq!(found, vec![Literal::Int(65)]);
1197
1198 let q3 = "Post filter .data->age >= 18 and .data->age < 65";
1199 let (h3, literals3) = canonicalize(q3).unwrap();
1200 let plan3 = planner::plan(q3).unwrap();
1201 assert!(matches!(plan3, PlanNode::ExprRangeScan { .. }));
1202 assert_eq!(count_literal_slots(&plan3), literals3.len());
1203 cache.insert(h3, plan3, literals3.len());
1204
1205 let q4 = "Post filter .data->age >= 25 and .data->age < 80";
1206 let (h4, literals4) = canonicalize(q4).unwrap();
1207 assert_eq!(h3, h4);
1208 let plan = cache.get_with_substitution(h4, &literals4).expect("hit");
1209 let mut found = Vec::new();
1210 collect_literals_for_test(&plan, &mut found);
1211 assert_eq!(found, vec![Literal::Int(25), Literal::Int(80)]);
1212 }
1213
1214 #[test]
1215 fn ordered_expression_scan_limit_offset_substitute_in_canonical_order() {
1216 let mut cache = PlanCache::new(8);
1217 let q1 = "Post order .data->age desc limit 10 offset 2";
1218 let (h1, literals1) = canonicalize(q1).unwrap();
1219 let plan1 = planner::plan(q1).unwrap();
1220 assert!(matches!(plan1, PlanNode::OrderedExprIndexScan { .. }));
1221 assert_eq!(count_literal_slots(&plan1), literals1.len());
1222 cache.insert(h1, plan1, literals1.len());
1223
1224 let q2 = "Post order .data->age desc limit 20 offset 3";
1225 let (h2, literals2) = canonicalize(q2).unwrap();
1226 assert_eq!(h1, h2);
1227 let plan = cache.get_with_substitution(h2, &literals2).expect("hit");
1228 let mut found = Vec::new();
1229 collect_literals_for_test(&plan, &mut found);
1230 assert_eq!(found, vec![Literal::Int(20), Literal::Int(3)]);
1231 }
1232
1233 #[test]
1234 fn test_update_by_pk_substitution() {
1235 let mut cache = PlanCache::new(100);
1236 let q1 = "User filter .id = 1 update { age := 100 }";
1237 let (h1, lits1) = canonicalize(q1).unwrap();
1238 cache.insert(h1, planner::plan(q1).unwrap(), lits1.len());
1239
1240 let q2 = "User filter .id = 7 update { age := 200 }";
1241 let (h2, lits2) = canonicalize(q2).unwrap();
1242 let plan = cache.get_with_substitution(h2, &lits2).expect("hit");
1243
1244 let mut found = Vec::new();
1245 collect_literals_for_test(&plan, &mut found);
1246 assert_eq!(found, vec![Literal::Int(7), Literal::Int(200)]);
1247 }
1248
1249 #[test]
1250 fn test_insert_substitution() {
1251 let mut cache = PlanCache::new(100);
1252 let q1 = r#"insert User { id := 1, name := "Alice", age := 20 }"#;
1253 let (h1, lits1) = canonicalize(q1).unwrap();
1254 cache.insert(h1, planner::plan(q1).unwrap(), lits1.len());
1255
1256 let q2 = r#"insert User { id := 2, name := "Bob", age := 30 }"#;
1257 let (h2, lits2) = canonicalize(q2).unwrap();
1258 let plan = cache.get_with_substitution(h2, &lits2).expect("hit");
1259
1260 let mut found = Vec::new();
1261 collect_literals_for_test(&plan, &mut found);
1262 assert_eq!(
1263 found,
1264 vec![
1265 Literal::Int(2),
1266 Literal::String("Bob".into()),
1267 Literal::Int(30),
1268 ]
1269 );
1270 }
1271
1272 #[test]
1276 fn test_insert_uuid_sugar_cacheable_and_substitutes() {
1277 let mut cache = PlanCache::new(100);
1278 let q1 = r#"insert User { id := uuid("00000000-0000-0000-0000-000000000001") }"#;
1279 let (h1, lits1) = canonicalize(q1).unwrap();
1280 assert_eq!(lits1.len(), 1, "the inner string is the only literal");
1281 let plan = planner::plan(q1).unwrap();
1282 assert_eq!(
1283 count_literal_slots(&plan),
1284 1,
1285 "Cast wrapping a Literal is a reachable substitution slot"
1286 );
1287 cache.insert(h1, plan, lits1.len());
1288 assert!(!cache.is_empty(), "uuid() insert must be cacheable");
1289
1290 let q2 = r#"insert User { id := uuid("00000000-0000-0000-0000-000000000002") }"#;
1291 let (h2, lits2) = canonicalize(q2).unwrap();
1292 assert_eq!(h1, h2, "same shape hashes identically");
1293 let subst = cache.get_with_substitution(h2, &lits2).expect("hit");
1294
1295 let mut found = Vec::new();
1296 collect_literals_for_test(&subst, &mut found);
1297 assert_eq!(
1298 found,
1299 vec![Literal::String(
1300 "00000000-0000-0000-0000-000000000002".into()
1301 )],
1302 "the second call's uuid must be substituted in, not the cached one"
1303 );
1304 }
1305
1306 #[test]
1311 fn test_two_arg_cast_uuid_not_cached() {
1312 let mut cache = PlanCache::new(100);
1313 let q = r#"User filter .id = cast(.other, "uuid")"#;
1314 let (h, lits) = canonicalize(q).unwrap();
1315 assert_eq!(
1316 lits.len(),
1317 1,
1318 "canonicalize collects the cast-target string"
1319 );
1320 let plan = planner::plan(q).unwrap();
1321 assert_eq!(
1322 count_literal_slots(&plan),
1323 0,
1324 "the cast target is baked into the AST, not a slot"
1325 );
1326 cache.insert(h, plan, lits.len());
1327 assert!(cache.is_empty(), "cast(x, \"uuid\") must not be cached");
1328 }
1329
1330 #[test]
1331 fn test_eviction_on_capacity() {
1332 let mut cache = PlanCache::new(2);
1333 let q1 = "User";
1334 let q2 = "User filter .age > 1";
1335 let _q3 = "User filter .age > 2";
1336 let q3_distinct = "User filter .id = 5";
1339
1340 let (h1, lits1) = canonicalize(q1).unwrap();
1341 let (h2, lits2) = canonicalize(q2).unwrap();
1342 let (h3, lits3) = canonicalize(q3_distinct).unwrap();
1343 cache.insert(h1, planner::plan(q1).unwrap(), lits1.len());
1344 cache.insert(h2, planner::plan(q2).unwrap(), lits2.len());
1345 cache.insert(h3, planner::plan(q3_distinct).unwrap(), lits3.len());
1347 assert!(cache.cache.contains_key(&h3));
1348 assert_eq!(cache.cache.len(), 1);
1349 }
1350
1351 fn collect_literals_for_test(plan: &PlanNode, out: &mut Vec<Literal>) {
1355 match plan {
1356 PlanNode::SeqScan { .. } => {}
1357 PlanNode::AliasScan { .. } => {}
1358 PlanNode::IndexScan { key, .. } => collect_expr_literals(key, out),
1359 PlanNode::RangeScan { start, end, .. } => {
1360 if let Some((expr, _)) = start {
1361 collect_expr_literals(expr, out);
1362 }
1363 if let Some((expr, _)) = end {
1364 collect_expr_literals(expr, out);
1365 }
1366 }
1367 PlanNode::ExprIndexScan { key, .. } => collect_expr_literals(key, out),
1368 PlanNode::ExprRangeScan { start, end, .. } => {
1369 if let Some((expr, _)) = start {
1370 collect_expr_literals(expr, out);
1371 }
1372 if let Some((expr, _)) = end {
1373 collect_expr_literals(expr, out);
1374 }
1375 }
1376 PlanNode::OrderedExprIndexScan { limit, offset, .. } => {
1377 collect_expr_literals(limit, out);
1378 if let Some(offset) = offset {
1379 collect_expr_literals(offset, out);
1380 }
1381 }
1382 PlanNode::Filter { input, predicate } => {
1383 collect_literals_for_test(input, out);
1384 collect_expr_literals(predicate, out);
1385 }
1386 PlanNode::Project { input, fields } => {
1387 collect_literals_for_test(input, out);
1388 for f in fields {
1389 collect_expr_literals(&f.expr, out);
1390 }
1391 }
1392 PlanNode::NestedProject { input, fields } => {
1393 fn collect_nested(nested: &crate::plan::NestedProjection, out: &mut Vec<Literal>) {
1394 if let Some(residual) = &nested.residual {
1395 collect_expr_literals(residual, out);
1396 }
1397 if let Some(limit) = &nested.limit {
1398 collect_expr_literals(limit, out);
1399 }
1400 if let Some(offset) = &nested.offset {
1401 collect_expr_literals(offset, out);
1402 }
1403 for field in &nested.fields {
1404 if let crate::plan::NestedField::Nested(inner) = field {
1405 collect_nested(inner, out);
1406 }
1407 }
1408 }
1409 collect_literals_for_test(input, out);
1410 for field in fields {
1411 match field {
1412 crate::plan::NestedProjectField::Plain(f) => {
1413 collect_expr_literals(&f.expr, out);
1414 }
1415 crate::plan::NestedProjectField::Nested(nested) => {
1416 collect_nested(nested, out);
1417 }
1418 }
1419 }
1420 }
1421 PlanNode::Sort { input, keys } => {
1422 collect_literals_for_test(input, out);
1423 for key in keys {
1424 collect_expr_literals(&key.expr, out);
1425 }
1426 }
1427 PlanNode::Limit { input, count } => {
1428 collect_literals_for_test(input, out);
1429 collect_expr_literals(count, out);
1430 }
1431 PlanNode::Offset { input, count } => {
1432 collect_literals_for_test(input, out);
1433 collect_expr_literals(count, out);
1434 }
1435 PlanNode::Aggregate {
1436 input, argument, ..
1437 } => {
1438 collect_literals_for_test(input, out);
1439 if let Some(argument) = argument {
1440 collect_expr_literals(argument, out);
1441 }
1442 }
1443 PlanNode::NestedLoopJoin {
1444 left, right, on, ..
1445 } => {
1446 collect_literals_for_test(left, out);
1447 collect_literals_for_test(right, out);
1448 if let Some(pred) = on {
1449 collect_expr_literals(pred, out);
1450 }
1451 }
1452 PlanNode::Insert { rows, .. } => {
1453 for assignments in rows {
1454 for a in assignments {
1455 collect_expr_literals(&a.value, out);
1456 }
1457 }
1458 }
1459 PlanNode::Upsert {
1460 assignments,
1461 on_conflict,
1462 ..
1463 } => {
1464 for a in assignments {
1465 collect_expr_literals(&a.value, out);
1466 }
1467 for a in on_conflict {
1468 collect_expr_literals(&a.value, out);
1469 }
1470 }
1471 PlanNode::Update {
1472 input, assignments, ..
1473 } => {
1474 collect_literals_for_test(input, out);
1475 for a in assignments {
1476 collect_expr_literals(&a.value, out);
1477 }
1478 }
1479 PlanNode::Distinct { input } => collect_literals_for_test(input, out),
1480 PlanNode::GroupBy {
1481 input,
1482 keys,
1483 aggregates,
1484 having,
1485 } => {
1486 collect_literals_for_test(input, out);
1487 for key in keys {
1488 collect_expr_literals(&key.expr, out);
1489 }
1490 for aggregate in aggregates {
1491 collect_expr_literals(&aggregate.argument, out);
1492 }
1493 if let Some(pred) = having {
1494 collect_expr_literals(pred, out);
1495 }
1496 }
1497 PlanNode::Delete { input, .. } => collect_literals_for_test(input, out),
1498 PlanNode::CreateTable { .. } => {}
1499 PlanNode::AlterTable { .. } => {}
1500 PlanNode::DropTable { .. } => {}
1501 PlanNode::CreateView { .. } => {}
1502 PlanNode::RefreshView { .. } => {}
1503 PlanNode::DropView { .. } => {}
1504 PlanNode::Window { input, windows } => {
1505 collect_literals_for_test(input, out);
1506 for w in windows {
1507 for arg in &w.args {
1508 collect_expr_literals(arg, out);
1509 }
1510 for expr in &w.partition_by {
1511 collect_expr_literals(expr, out);
1512 }
1513 for key in &w.order_by {
1514 collect_expr_literals(&key.expr, out);
1515 }
1516 }
1517 }
1518 PlanNode::Union { left, right, .. } => {
1519 collect_literals_for_test(left, out);
1520 collect_literals_for_test(right, out);
1521 }
1522 PlanNode::Explain { input } => {
1523 collect_literals_for_test(input, out);
1524 }
1525 PlanNode::ListTypes | PlanNode::Describe { .. } => {}
1526 PlanNode::Begin | PlanNode::Commit | PlanNode::Rollback => {}
1527 }
1528 }
1529
1530 fn collect_expr_literals(expr: &Expr, out: &mut Vec<Literal>) {
1531 match expr {
1532 Expr::Literal(l) => out.push(l.clone()),
1533 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::Param(_) => {}
1534 Expr::BinaryOp(l, _, r) => {
1535 collect_expr_literals(l, out);
1536 collect_expr_literals(r, out);
1537 }
1538 Expr::UnaryOp(_, inner) => collect_expr_literals(inner, out),
1539 Expr::FunctionCall(_, inner, _) => collect_expr_literals(inner, out),
1540 Expr::Coalesce(l, r) => {
1541 collect_expr_literals(l, out);
1542 collect_expr_literals(r, out);
1543 }
1544 Expr::InList { expr, list, .. } => {
1545 collect_expr_literals(expr, out);
1546 for item in list {
1547 collect_expr_literals(item, out);
1548 }
1549 }
1550 Expr::ScalarFunc(_, args) => {
1551 for a in args {
1552 collect_expr_literals(a, out);
1553 }
1554 }
1555 Expr::Cast(inner, _) => collect_expr_literals(inner, out),
1556 Expr::Case { whens, else_expr } => {
1557 for (cond, result) in whens {
1558 collect_expr_literals(cond, out);
1559 collect_expr_literals(result, out);
1560 }
1561 if let Some(e) = else_expr {
1562 collect_expr_literals(e, out);
1563 }
1564 }
1565 Expr::InSubquery { expr, .. } => {
1566 collect_expr_literals(expr, out);
1567 }
1568 Expr::ExistsSubquery { .. } => {}
1569 Expr::Window {
1570 args,
1571 partition_by,
1572 order_by,
1573 ..
1574 } => {
1575 for a in args {
1576 collect_expr_literals(a, out);
1577 }
1578 for expr in partition_by {
1579 collect_expr_literals(expr, out);
1580 }
1581 for key in order_by {
1582 collect_expr_literals(&key.expr, out);
1583 }
1584 }
1585 Expr::JsonPath { base, .. } => collect_expr_literals(base, out),
1588 Expr::ValueLit(_) => {}
1589 Expr::Null => {}
1590 Expr::NestedQuery(_) => {}
1592 }
1593 }
1594}