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