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