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 count_literal_slots(&plan) != source_literal_count {
73 return;
74 }
75 if self.cache.len() >= self.capacity && !self.cache.contains_key(&hash) {
76 self.cache.clear();
81 }
82 self.cache.insert(hash, plan);
83 }
84
85 pub fn get_with_substitution(&mut self, hash: u64, literals: &[Literal]) -> Option<PlanNode> {
97 match self.cache.get(&hash) {
98 Some(template) => {
99 self.hits += 1;
100 let mut plan = template.clone();
101 let mut idx = 0usize;
102 substitute_plan(&mut plan, literals, &mut idx);
103 debug_assert_eq!(
104 idx,
105 literals.len(),
106 "plan substitution consumed {idx} literals but query had {}",
107 literals.len(),
108 );
109 Some(plan)
110 }
111 None => {
112 self.misses += 1;
113 None
114 }
115 }
116 }
117
118 pub fn len(&self) -> usize {
119 self.cache.len()
120 }
121
122 pub fn is_empty(&self) -> bool {
123 self.cache.is_empty()
124 }
125
126 pub fn clear(&mut self) {
127 self.cache.clear();
128 }
129}
130
131pub(crate) fn substitute_plan(plan: &mut PlanNode, literals: &[Literal], idx: &mut usize) {
150 match plan {
151 PlanNode::SeqScan { .. } => {}
152 PlanNode::AliasScan { .. } => {}
153 PlanNode::IndexScan { key, .. } => {
154 substitute_expr(key, literals, idx);
155 }
156 PlanNode::RangeScan { start, end, .. } => {
157 if let Some((expr, _)) = start {
158 substitute_expr(expr, literals, idx);
159 }
160 if let Some((expr, _)) = end {
161 substitute_expr(expr, literals, idx);
162 }
163 }
164 PlanNode::Filter { input, predicate } => {
165 substitute_plan(input, literals, idx);
166 substitute_expr(predicate, literals, idx);
167 }
168 PlanNode::Project { input, fields } => {
169 substitute_plan(input, literals, idx);
170 for f in fields {
171 substitute_expr(&mut f.expr, literals, idx);
172 }
173 }
174 PlanNode::Sort { input, .. } => substitute_plan(input, literals, idx),
175 PlanNode::AlterTable { .. } => {}
176 PlanNode::DropTable { .. } => {}
177 PlanNode::Limit { input, count } => {
178 if let PlanNode::Offset {
187 input: inner,
188 count: off_count,
189 } = input.as_mut()
190 {
191 substitute_plan(inner, literals, idx);
192 substitute_expr(count, literals, idx);
193 substitute_expr(off_count, literals, idx);
194 } else {
195 substitute_plan(input, literals, idx);
196 substitute_expr(count, literals, idx);
197 }
198 }
199 PlanNode::Offset { input, count } => {
200 substitute_plan(input, literals, idx);
203 substitute_expr(count, literals, idx);
204 }
205 PlanNode::Aggregate { input, .. } => {
206 substitute_plan(input, literals, idx);
207 }
208 PlanNode::NestedLoopJoin {
209 left, right, on, ..
210 } => {
211 substitute_plan(left, literals, idx);
216 substitute_plan(right, literals, idx);
217 if let Some(pred) = on {
218 substitute_expr(pred, literals, idx);
219 }
220 }
221 PlanNode::Distinct { input } => {
222 substitute_plan(input, literals, idx);
223 }
224 PlanNode::GroupBy { input, having, .. } => {
225 substitute_plan(input, literals, idx);
226 if let Some(pred) = having {
227 substitute_expr(pred, literals, idx);
228 }
229 }
230 PlanNode::Insert { rows, .. } => {
231 for assignments in rows {
232 substitute_assignments(assignments, literals, idx);
233 }
234 }
235 PlanNode::Upsert {
236 assignments,
237 on_conflict,
238 ..
239 } => {
240 substitute_assignments(assignments, literals, idx);
241 substitute_assignments(on_conflict, literals, idx);
242 }
243 PlanNode::Update {
244 input, assignments, ..
245 } => {
246 substitute_plan(input, literals, idx);
247 substitute_assignments(assignments, literals, idx);
248 }
249 PlanNode::Delete { input, .. } => {
250 substitute_plan(input, literals, idx);
251 }
252 PlanNode::CreateTable { .. } => {}
253 PlanNode::CreateView { .. } => {}
254 PlanNode::RefreshView { .. } => {}
255 PlanNode::DropView { .. } => {}
256 PlanNode::Window { input, windows } => {
257 substitute_plan(input, literals, idx);
258 for w in windows {
259 for arg in &mut w.args {
260 substitute_expr(arg, literals, idx);
261 }
262 }
263 }
264 PlanNode::Union { left, right, .. } => {
265 substitute_plan(left, literals, idx);
266 substitute_plan(right, literals, idx);
267 }
268 PlanNode::Explain { input } => {
269 substitute_plan(input, literals, idx);
270 }
271 PlanNode::ListTypes | PlanNode::Describe { .. } => {}
272 PlanNode::Begin | PlanNode::Commit | PlanNode::Rollback => {}
273 }
274}
275
276fn substitute_assignments(assignments: &mut [Assignment], literals: &[Literal], idx: &mut usize) {
277 for a in assignments {
278 substitute_expr(&mut a.value, literals, idx);
279 }
280}
281
282pub(crate) fn count_literal_slots(plan: &PlanNode) -> usize {
288 let mut n = 0usize;
289 count_plan(plan, &mut n);
290 n
291}
292
293fn count_plan(plan: &PlanNode, n: &mut usize) {
294 match plan {
295 PlanNode::SeqScan { .. } => {}
296 PlanNode::AliasScan { .. } => {}
297 PlanNode::IndexScan { key, .. } => count_expr(key, n),
298 PlanNode::RangeScan { start, end, .. } => {
299 if let Some((expr, _)) = start {
300 count_expr(expr, n);
301 }
302 if let Some((expr, _)) = end {
303 count_expr(expr, n);
304 }
305 }
306 PlanNode::Filter { input, predicate } => {
307 count_plan(input, n);
308 count_expr(predicate, n);
309 }
310 PlanNode::Project { input, fields } => {
311 count_plan(input, n);
312 for f in fields {
313 count_expr(&f.expr, n);
314 }
315 }
316 PlanNode::Sort { input, .. } => count_plan(input, n),
317 PlanNode::Limit { input, count } => {
318 if let PlanNode::Offset {
323 input: inner,
324 count: off_count,
325 } = input.as_ref()
326 {
327 count_plan(inner, n);
328 count_expr(count, n);
329 count_expr(off_count, n);
330 } else {
331 count_plan(input, n);
332 count_expr(count, n);
333 }
334 }
335 PlanNode::Offset { input, count } => {
336 count_plan(input, n);
337 count_expr(count, n);
338 }
339 PlanNode::Aggregate { input, .. } => count_plan(input, n),
340 PlanNode::NestedLoopJoin {
341 left, right, on, ..
342 } => {
343 count_plan(left, n);
344 count_plan(right, n);
345 if let Some(pred) = on {
346 count_expr(pred, n);
347 }
348 }
349 PlanNode::Distinct { input } => count_plan(input, n),
350 PlanNode::GroupBy { input, having, .. } => {
351 count_plan(input, n);
352 if let Some(pred) = having {
353 count_expr(pred, n);
354 }
355 }
356 PlanNode::Insert { rows, .. } => {
357 for assignments in rows {
358 for a in assignments {
359 count_expr(&a.value, n);
360 }
361 }
362 }
363 PlanNode::Upsert {
364 assignments,
365 on_conflict,
366 ..
367 } => {
368 for a in assignments {
369 count_expr(&a.value, n);
370 }
371 for a in on_conflict {
372 count_expr(&a.value, n);
373 }
374 }
375 PlanNode::Update {
376 input, assignments, ..
377 } => {
378 count_plan(input, n);
379 for a in assignments {
380 count_expr(&a.value, n);
381 }
382 }
383 PlanNode::Delete { input, .. } => count_plan(input, n),
384 PlanNode::CreateTable { .. } => {}
385 PlanNode::AlterTable { .. } => {}
386 PlanNode::DropTable { .. } => {}
387 PlanNode::CreateView { .. } => {}
388 PlanNode::RefreshView { .. } => {}
389 PlanNode::DropView { .. } => {}
390 PlanNode::Window { input, windows } => {
391 count_plan(input, n);
392 for w in windows {
393 for arg in &w.args {
394 count_expr(arg, n);
395 }
396 }
397 }
398 PlanNode::Union { left, right, .. } => {
399 count_plan(left, n);
400 count_plan(right, n);
401 }
402 PlanNode::Explain { input } => {
403 count_plan(input, n);
404 }
405 PlanNode::ListTypes | PlanNode::Describe { .. } => {}
406 PlanNode::Begin | PlanNode::Commit | PlanNode::Rollback => {}
407 }
408}
409
410fn count_expr(expr: &Expr, n: &mut usize) {
411 match expr {
412 Expr::Literal(_) => *n += 1,
413 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::Param(_) => {}
414 Expr::BinaryOp(l, _, r) => {
415 count_expr(l, n);
416 count_expr(r, n);
417 }
418 Expr::UnaryOp(_, inner) => count_expr(inner, n),
419 Expr::FunctionCall(_, inner) => count_expr(inner, n),
420 Expr::Coalesce(l, r) => {
421 count_expr(l, n);
422 count_expr(r, n);
423 }
424 Expr::InList { expr, list, .. } => {
425 count_expr(expr, n);
426 for item in list {
427 count_expr(item, n);
428 }
429 }
430 Expr::ScalarFunc(_, args) => {
431 for a in args {
432 count_expr(a, n);
433 }
434 }
435 Expr::Cast(inner, _) => count_expr(inner, n),
436 Expr::Case { whens, else_expr } => {
437 for (cond, result) in whens {
438 count_expr(cond, n);
439 count_expr(result, n);
440 }
441 if let Some(e) = else_expr {
442 count_expr(e, n);
443 }
444 }
445 Expr::InSubquery { expr, .. } => {
446 count_expr(expr, n);
447 }
450 Expr::ExistsSubquery { .. } => {
451 }
454 Expr::Window { args, .. } => {
455 for a in args {
456 count_expr(a, n);
457 }
458 }
459 Expr::ValueLit(_) => {}
462 Expr::Null => {}
463 }
464}
465
466fn substitute_expr(expr: &mut Expr, literals: &[Literal], idx: &mut usize) {
467 match expr {
468 Expr::Literal(_) => {
469 *expr = Expr::Literal(literals[*idx].clone());
473 *idx += 1;
474 }
475 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::Param(_) => {}
476 Expr::BinaryOp(l, _, r) => {
477 substitute_expr(l, literals, idx);
478 substitute_expr(r, literals, idx);
479 }
480 Expr::UnaryOp(_, inner) => {
481 substitute_expr(inner, literals, idx);
482 }
483 Expr::FunctionCall(_, inner) => {
484 substitute_expr(inner, literals, idx);
485 }
486 Expr::Coalesce(l, r) => {
487 substitute_expr(l, literals, idx);
488 substitute_expr(r, literals, idx);
489 }
490 Expr::InList { expr, list, .. } => {
491 substitute_expr(expr, literals, idx);
492 for item in list {
493 substitute_expr(item, literals, idx);
494 }
495 }
496 Expr::ScalarFunc(_, args) => {
497 for a in args {
498 substitute_expr(a, literals, idx);
499 }
500 }
501 Expr::Cast(inner, _) => substitute_expr(inner, literals, idx),
502 Expr::Case { whens, else_expr } => {
503 for (cond, result) in whens {
504 substitute_expr(cond, literals, idx);
505 substitute_expr(result, literals, idx);
506 }
507 if let Some(e) = else_expr {
508 substitute_expr(e, literals, idx);
509 }
510 }
511 Expr::InSubquery { expr, .. } => {
512 substitute_expr(expr, literals, idx);
513 }
514 Expr::ExistsSubquery { .. } => {
515 }
518 Expr::Window { args, .. } => {
519 for a in args {
520 substitute_expr(a, literals, idx);
521 }
522 }
523 Expr::ValueLit(_) => {}
526 Expr::Null => {}
527 }
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533 use crate::canonicalize::canonicalize;
534 use crate::planner;
535
536 #[test]
537 fn test_cache_hit_substitutes_literal() {
538 let mut cache = PlanCache::new(100);
539
540 let q1 = "User filter .id = 42";
542 let (h1, lits1) = canonicalize(q1).unwrap();
543 let p1 = planner::plan(q1).unwrap();
544 cache.insert(h1, p1, lits1.len());
545
546 let q2 = "User filter .id = 99";
549 let (h2, lits2) = canonicalize(q2).unwrap();
550 assert_eq!(h1, h2, "different literals must hash the same");
551
552 let plan = cache.get_with_substitution(h2, &lits2).expect("hit");
553
554 match plan {
556 PlanNode::IndexScan { key, .. } => {
557 assert_eq!(key, Expr::Literal(Literal::Int(99)));
558 }
559 other => panic!("expected IndexScan, got {other:?}"),
560 }
561
562 assert_eq!(lits1, vec![Literal::Int(42)]);
565 assert_eq!(cache.hits, 1);
566 assert_eq!(cache.misses, 0);
567 }
568
569 #[test]
570 fn test_subquery_plan_not_cached() {
571 let mut cache = PlanCache::new(100);
578 let q = "User filter .id in (Ord filter .total > 100 { .user_id })";
579 let (h, lits) = canonicalize(q).unwrap();
580 assert_eq!(lits.len(), 1, "canonicalize collects the inner literal");
581 let plan = planner::plan(q).unwrap();
582 assert_eq!(
583 count_literal_slots(&plan),
584 0,
585 "the subquery literal is not a reachable substitution slot"
586 );
587 cache.insert(h, plan, lits.len());
588 assert!(cache.is_empty(), "subquery plans must not be cached (#137)");
589 assert!(cache.get_with_substitution(h, &lits).is_none());
590 }
591
592 #[test]
593 fn test_cache_miss_returns_none_and_bumps_counter() {
594 let mut cache = PlanCache::new(100);
595 assert!(cache.get_with_substitution(99999, &[]).is_none());
596 assert_eq!(cache.misses, 1);
597 assert_eq!(cache.hits, 0);
598 }
599
600 #[test]
601 fn test_multi_literal_filter_substitution() {
602 let mut cache = PlanCache::new(100);
603 let q1 = r#"User filter .age > 30 and .status = "active" { .name }"#;
604 let (h1, lits1) = canonicalize(q1).unwrap();
605 cache.insert(h1, planner::plan(q1).unwrap(), lits1.len());
606
607 let q2 = r#"User filter .age > 50 and .status = "pending" { .name }"#;
608 let (h2, lits2) = canonicalize(q2).unwrap();
609 let plan = cache.get_with_substitution(h2, &lits2).expect("hit");
610
611 let mut found = Vec::new();
613 collect_literals_for_test(&plan, &mut found);
614 assert_eq!(
615 found,
616 vec![Literal::Int(50), Literal::String("pending".into()),]
617 );
618 }
619
620 #[test]
621 fn test_update_by_pk_substitution() {
622 let mut cache = PlanCache::new(100);
623 let q1 = "User filter .id = 1 update { age := 100 }";
624 let (h1, lits1) = canonicalize(q1).unwrap();
625 cache.insert(h1, planner::plan(q1).unwrap(), lits1.len());
626
627 let q2 = "User filter .id = 7 update { age := 200 }";
628 let (h2, lits2) = canonicalize(q2).unwrap();
629 let plan = cache.get_with_substitution(h2, &lits2).expect("hit");
630
631 let mut found = Vec::new();
632 collect_literals_for_test(&plan, &mut found);
633 assert_eq!(found, vec![Literal::Int(7), Literal::Int(200)]);
634 }
635
636 #[test]
637 fn test_insert_substitution() {
638 let mut cache = PlanCache::new(100);
639 let q1 = r#"insert User { id := 1, name := "Alice", age := 20 }"#;
640 let (h1, lits1) = canonicalize(q1).unwrap();
641 cache.insert(h1, planner::plan(q1).unwrap(), lits1.len());
642
643 let q2 = r#"insert User { id := 2, name := "Bob", age := 30 }"#;
644 let (h2, lits2) = canonicalize(q2).unwrap();
645 let plan = cache.get_with_substitution(h2, &lits2).expect("hit");
646
647 let mut found = Vec::new();
648 collect_literals_for_test(&plan, &mut found);
649 assert_eq!(
650 found,
651 vec![
652 Literal::Int(2),
653 Literal::String("Bob".into()),
654 Literal::Int(30),
655 ]
656 );
657 }
658
659 #[test]
663 fn test_insert_uuid_sugar_cacheable_and_substitutes() {
664 let mut cache = PlanCache::new(100);
665 let q1 = r#"insert User { id := uuid("00000000-0000-0000-0000-000000000001") }"#;
666 let (h1, lits1) = canonicalize(q1).unwrap();
667 assert_eq!(lits1.len(), 1, "the inner string is the only literal");
668 let plan = planner::plan(q1).unwrap();
669 assert_eq!(
670 count_literal_slots(&plan),
671 1,
672 "Cast wrapping a Literal is a reachable substitution slot"
673 );
674 cache.insert(h1, plan, lits1.len());
675 assert!(!cache.is_empty(), "uuid() insert must be cacheable");
676
677 let q2 = r#"insert User { id := uuid("00000000-0000-0000-0000-000000000002") }"#;
678 let (h2, lits2) = canonicalize(q2).unwrap();
679 assert_eq!(h1, h2, "same shape hashes identically");
680 let subst = cache.get_with_substitution(h2, &lits2).expect("hit");
681
682 let mut found = Vec::new();
683 collect_literals_for_test(&subst, &mut found);
684 assert_eq!(
685 found,
686 vec![Literal::String(
687 "00000000-0000-0000-0000-000000000002".into()
688 )],
689 "the second call's uuid must be substituted in, not the cached one"
690 );
691 }
692
693 #[test]
698 fn test_two_arg_cast_uuid_not_cached() {
699 let mut cache = PlanCache::new(100);
700 let q = r#"User filter .id = cast(.other, "uuid")"#;
701 let (h, lits) = canonicalize(q).unwrap();
702 assert_eq!(
703 lits.len(),
704 1,
705 "canonicalize collects the cast-target string"
706 );
707 let plan = planner::plan(q).unwrap();
708 assert_eq!(
709 count_literal_slots(&plan),
710 0,
711 "the cast target is baked into the AST, not a slot"
712 );
713 cache.insert(h, plan, lits.len());
714 assert!(cache.is_empty(), "cast(x, \"uuid\") must not be cached");
715 }
716
717 #[test]
718 fn test_eviction_on_capacity() {
719 let mut cache = PlanCache::new(2);
720 let q1 = "User";
721 let q2 = "User filter .age > 1";
722 let _q3 = "User filter .age > 2";
723 let q3_distinct = "User filter .id = 5";
726
727 let (h1, lits1) = canonicalize(q1).unwrap();
728 let (h2, lits2) = canonicalize(q2).unwrap();
729 let (h3, lits3) = canonicalize(q3_distinct).unwrap();
730 cache.insert(h1, planner::plan(q1).unwrap(), lits1.len());
731 cache.insert(h2, planner::plan(q2).unwrap(), lits2.len());
732 cache.insert(h3, planner::plan(q3_distinct).unwrap(), lits3.len());
734 assert!(cache.cache.contains_key(&h3));
735 assert_eq!(cache.cache.len(), 1);
736 }
737
738 fn collect_literals_for_test(plan: &PlanNode, out: &mut Vec<Literal>) {
742 match plan {
743 PlanNode::SeqScan { .. } => {}
744 PlanNode::AliasScan { .. } => {}
745 PlanNode::IndexScan { key, .. } => collect_expr_literals(key, out),
746 PlanNode::RangeScan { start, end, .. } => {
747 if let Some((expr, _)) = start {
748 collect_expr_literals(expr, out);
749 }
750 if let Some((expr, _)) = end {
751 collect_expr_literals(expr, out);
752 }
753 }
754 PlanNode::Filter { input, predicate } => {
755 collect_literals_for_test(input, out);
756 collect_expr_literals(predicate, out);
757 }
758 PlanNode::Project { input, fields } => {
759 collect_literals_for_test(input, out);
760 for f in fields {
761 collect_expr_literals(&f.expr, out);
762 }
763 }
764 PlanNode::Sort { input, .. } => collect_literals_for_test(input, out),
765 PlanNode::Limit { input, count } => {
766 collect_literals_for_test(input, out);
767 collect_expr_literals(count, out);
768 }
769 PlanNode::Offset { input, count } => {
770 collect_literals_for_test(input, out);
771 collect_expr_literals(count, out);
772 }
773 PlanNode::Aggregate { input, .. } => collect_literals_for_test(input, out),
774 PlanNode::NestedLoopJoin {
775 left, right, on, ..
776 } => {
777 collect_literals_for_test(left, out);
778 collect_literals_for_test(right, out);
779 if let Some(pred) = on {
780 collect_expr_literals(pred, out);
781 }
782 }
783 PlanNode::Insert { rows, .. } => {
784 for assignments in rows {
785 for a in assignments {
786 collect_expr_literals(&a.value, out);
787 }
788 }
789 }
790 PlanNode::Upsert {
791 assignments,
792 on_conflict,
793 ..
794 } => {
795 for a in assignments {
796 collect_expr_literals(&a.value, out);
797 }
798 for a in on_conflict {
799 collect_expr_literals(&a.value, out);
800 }
801 }
802 PlanNode::Update {
803 input, assignments, ..
804 } => {
805 collect_literals_for_test(input, out);
806 for a in assignments {
807 collect_expr_literals(&a.value, out);
808 }
809 }
810 PlanNode::Distinct { input } => collect_literals_for_test(input, out),
811 PlanNode::GroupBy { input, having, .. } => {
812 collect_literals_for_test(input, out);
813 if let Some(pred) = having {
814 collect_expr_literals(pred, out);
815 }
816 }
817 PlanNode::Delete { input, .. } => collect_literals_for_test(input, out),
818 PlanNode::CreateTable { .. } => {}
819 PlanNode::AlterTable { .. } => {}
820 PlanNode::DropTable { .. } => {}
821 PlanNode::CreateView { .. } => {}
822 PlanNode::RefreshView { .. } => {}
823 PlanNode::DropView { .. } => {}
824 PlanNode::Window { input, windows } => {
825 collect_literals_for_test(input, out);
826 for w in windows {
827 for arg in &w.args {
828 collect_expr_literals(arg, out);
829 }
830 }
831 }
832 PlanNode::Union { left, right, .. } => {
833 collect_literals_for_test(left, out);
834 collect_literals_for_test(right, out);
835 }
836 PlanNode::Explain { input } => {
837 collect_literals_for_test(input, out);
838 }
839 PlanNode::ListTypes | PlanNode::Describe { .. } => {}
840 PlanNode::Begin | PlanNode::Commit | PlanNode::Rollback => {}
841 }
842 }
843
844 fn collect_expr_literals(expr: &Expr, out: &mut Vec<Literal>) {
845 match expr {
846 Expr::Literal(l) => out.push(l.clone()),
847 Expr::Field(_) | Expr::QualifiedField { .. } | Expr::Param(_) => {}
848 Expr::BinaryOp(l, _, r) => {
849 collect_expr_literals(l, out);
850 collect_expr_literals(r, out);
851 }
852 Expr::UnaryOp(_, inner) => collect_expr_literals(inner, out),
853 Expr::FunctionCall(_, inner) => collect_expr_literals(inner, out),
854 Expr::Coalesce(l, r) => {
855 collect_expr_literals(l, out);
856 collect_expr_literals(r, out);
857 }
858 Expr::InList { expr, list, .. } => {
859 collect_expr_literals(expr, out);
860 for item in list {
861 collect_expr_literals(item, out);
862 }
863 }
864 Expr::ScalarFunc(_, args) => {
865 for a in args {
866 collect_expr_literals(a, out);
867 }
868 }
869 Expr::Cast(inner, _) => collect_expr_literals(inner, out),
870 Expr::Case { whens, else_expr } => {
871 for (cond, result) in whens {
872 collect_expr_literals(cond, out);
873 collect_expr_literals(result, out);
874 }
875 if let Some(e) = else_expr {
876 collect_expr_literals(e, out);
877 }
878 }
879 Expr::InSubquery { expr, .. } => {
880 collect_expr_literals(expr, out);
881 }
882 Expr::ExistsSubquery { .. } => {}
883 Expr::Window { args, .. } => {
884 for a in args {
885 collect_expr_literals(a, out);
886 }
887 }
888 Expr::ValueLit(_) => {}
889 Expr::Null => {}
890 }
891 }
892}