1use super::{ScalarExpr, ScalarFrameBound};
10
11impl ScalarExpr {
12 pub fn visit(&self, visitor: &mut impl FnMut(&Self)) {
14 self.try_visit(&mut |expression| {
15 visitor(expression);
16 Ok::<_, std::convert::Infallible>(true)
17 })
18 .unwrap_or_else(|never| match never {});
19 }
20
21 fn try_visit_window<E>(
23 args: &[Self],
24 filter: Option<&Self>,
25 spec: &super::ScalarWindowSpec,
26 visitor: &mut impl FnMut(&Self) -> Result<bool, E>,
27 ) -> Result<(), E> {
28 for expression in args
29 .iter()
30 .chain(filter)
31 .chain(&spec.partition_by)
32 .chain(spec.order_by.iter().map(|order| &order.expr))
33 {
34 expression.try_visit(visitor)?;
35 }
36 for bound in spec
37 .frame
38 .iter()
39 .flat_map(|frame| [&frame.start, &frame.end])
40 {
41 if let ScalarFrameBound::Preceding(expression)
42 | ScalarFrameBound::Following(expression) = bound
43 {
44 expression.try_visit(visitor)?;
45 }
46 }
47 Ok(())
48 }
49
50 pub fn try_visit<E>(
52 &self,
53 visitor: &mut impl FnMut(&Self) -> Result<bool, E>,
54 ) -> Result<(), E> {
55 if !visitor(self)? {
56 return Ok(());
57 }
58 match self {
59 Self::And(parts)
60 | Self::Or(parts)
61 | Self::Array(parts)
62 | Self::Row(parts)
63 | Self::CompositeRow { items: parts, .. } => {
64 for part in parts {
65 part.try_visit(visitor)?;
66 }
67 }
68 Self::Not(inner)
69 | Self::UnaryMinus(inner)
70 | Self::Cast { expr: inner, .. }
71 | Self::Collated { expr: inner, .. }
72 | Self::IsNull { expr: inner, .. }
73 | Self::InSubquery { expr: inner, .. } => inner.try_visit(visitor)?,
74 Self::Binary { lhs, rhs, .. } => {
75 lhs.try_visit(visitor)?;
76 rhs.try_visit(visitor)?;
77 }
78 Self::Between { expr, low, high } => {
79 expr.try_visit(visitor)?;
80 low.try_visit(visitor)?;
81 high.try_visit(visitor)?;
82 }
83 Self::InList { expr, list, .. } => {
84 expr.try_visit(visitor)?;
85 for part in list {
86 part.try_visit(visitor)?;
87 }
88 }
89 Self::Func {
90 args,
91 order_by,
92 filter,
93 ..
94 } => {
95 for argument in args {
96 argument.try_visit(visitor)?;
97 }
98 for order in order_by {
99 order.expr.try_visit(visitor)?;
100 }
101 if let Some(filter) = filter {
102 filter.try_visit(visitor)?;
103 }
104 }
105 Self::WindowCall {
106 args, spec, filter, ..
107 } => Self::try_visit_window(args, filter.as_deref(), spec, visitor)?,
108 Self::Case {
109 base,
110 when,
111 else_branch,
112 } => {
113 if let Some(base) = base {
114 base.try_visit(visitor)?;
115 }
116 for (condition, result) in when {
117 condition.try_visit(visitor)?;
118 result.try_visit(visitor)?;
119 }
120 if let Some(else_branch) = else_branch {
121 else_branch.try_visit(visitor)?;
122 }
123 }
124 Self::Default
125 | Self::Star
126 | Self::QualifiedStar(_)
127 | Self::Column(_)
128 | Self::Position(_)
129 | Self::InternalColumn(_)
130 | Self::QualifiedColumn { .. }
131 | Self::Literal(_)
132 | Self::TypedLiteral { .. }
133 | Self::Param(_)
134 | Self::ScalarSubquery(_)
135 | Self::Exists { .. } => {}
136 }
137 Ok(())
138 }
139
140 fn visit_window_mut(
142 args: &mut [Self],
143 filter: Option<&mut Self>,
144 spec: &mut super::ScalarWindowSpec,
145 visitor: &mut impl FnMut(&mut Self),
146 ) {
147 for expression in args
148 .iter_mut()
149 .chain(filter)
150 .chain(&mut spec.partition_by)
151 .chain(spec.order_by.iter_mut().map(|order| &mut order.expr))
152 {
153 expression.visit_mut(visitor);
154 }
155 for bound in spec
156 .frame
157 .iter_mut()
158 .flat_map(|frame| [&mut frame.start, &mut frame.end])
159 {
160 if let ScalarFrameBound::Preceding(expression)
161 | ScalarFrameBound::Following(expression) = bound
162 {
163 expression.visit_mut(visitor);
164 }
165 }
166 }
167
168 pub fn visit_mut(&mut self, visitor: &mut impl FnMut(&mut Self)) {
170 visitor(self);
171 match self {
172 Self::And(parts)
173 | Self::Or(parts)
174 | Self::Array(parts)
175 | Self::Row(parts)
176 | Self::CompositeRow { items: parts, .. } => {
177 for part in parts {
178 part.visit_mut(visitor);
179 }
180 }
181 Self::Not(inner)
182 | Self::UnaryMinus(inner)
183 | Self::Cast { expr: inner, .. }
184 | Self::Collated { expr: inner, .. }
185 | Self::IsNull { expr: inner, .. }
186 | Self::InSubquery { expr: inner, .. } => inner.visit_mut(visitor),
187 Self::Binary { lhs, rhs, .. } => {
188 lhs.visit_mut(visitor);
189 rhs.visit_mut(visitor);
190 }
191 Self::Between { expr, low, high } => {
192 expr.visit_mut(visitor);
193 low.visit_mut(visitor);
194 high.visit_mut(visitor);
195 }
196 Self::InList { expr, list, .. } => {
197 expr.visit_mut(visitor);
198 for part in list {
199 part.visit_mut(visitor);
200 }
201 }
202 Self::Func {
203 args,
204 order_by,
205 filter,
206 ..
207 } => {
208 for argument in args {
209 argument.visit_mut(visitor);
210 }
211 for order in order_by {
212 order.expr.visit_mut(visitor);
213 }
214 if let Some(filter) = filter {
215 filter.visit_mut(visitor);
216 }
217 }
218 Self::WindowCall {
219 args, spec, filter, ..
220 } => Self::visit_window_mut(args, filter.as_deref_mut(), spec, visitor),
221 Self::Case {
222 base,
223 when,
224 else_branch,
225 } => {
226 if let Some(base) = base {
227 base.visit_mut(visitor);
228 }
229 for (condition, result) in when {
230 condition.visit_mut(visitor);
231 result.visit_mut(visitor);
232 }
233 if let Some(else_branch) = else_branch {
234 else_branch.visit_mut(visitor);
235 }
236 }
237 Self::Default
238 | Self::Star
239 | Self::QualifiedStar(_)
240 | Self::Column(_)
241 | Self::Position(_)
242 | Self::InternalColumn(_)
243 | Self::QualifiedColumn { .. }
244 | Self::Literal(_)
245 | Self::TypedLiteral { .. }
246 | Self::Param(_)
247 | Self::ScalarSubquery(_)
248 | Self::Exists { .. } => {}
249 }
250 }
251
252 pub fn collect_columns(&self, output: &mut std::collections::BTreeSet<String>) -> bool {
254 match self.try_visit_columns(&mut |name| {
255 output.insert(name.to_owned());
256 Ok::<_, std::convert::Infallible>(())
257 }) {
258 Ok(projectable) => projectable,
259 Err(never) => match never {},
260 }
261 }
262
263 pub fn try_visit_columns<'a, E>(
265 &'a self,
266 visitor: &mut impl FnMut(&'a str) -> Result<(), E>,
267 ) -> Result<bool, E> {
268 match self {
269 Self::Column(name) | Self::QualifiedColumn { column: name, .. } => {
270 visitor(name)?;
271 Ok(true)
272 }
273 Self::Literal(_)
274 | Self::TypedLiteral { .. }
275 | Self::Param(_)
276 | Self::InternalColumn(_) => Ok(true),
277 Self::Func {
278 args,
279 order_by,
280 filter,
281 ..
282 } => {
283 for expression in args
284 .iter()
285 .chain(order_by.iter().map(|order| &order.expr))
286 .chain(filter.as_deref())
287 {
288 if !expression.try_visit_columns(visitor)? {
289 return Ok(false);
290 }
291 }
292 Ok(true)
293 }
294 Self::Array(items)
295 | Self::Row(items)
296 | Self::CompositeRow { items, .. }
297 | Self::And(items)
298 | Self::Or(items) => {
299 for item in items {
300 if !item.try_visit_columns(visitor)? {
301 return Ok(false);
302 }
303 }
304 Ok(true)
305 }
306 Self::Binary { lhs, rhs, .. } => {
307 Ok(lhs.try_visit_columns(visitor)? && rhs.try_visit_columns(visitor)?)
308 }
309 Self::UnaryMinus(expr)
310 | Self::Not(expr)
311 | Self::IsNull { expr, .. }
312 | Self::Cast { expr, .. }
313 | Self::Collated { expr, .. } => expr.try_visit_columns(visitor),
314 Self::Between { expr, low, high } => Ok(expr.try_visit_columns(visitor)?
315 && low.try_visit_columns(visitor)?
316 && high.try_visit_columns(visitor)?),
317 Self::InList { expr, list, .. } => {
318 for item in std::iter::once(expr.as_ref()).chain(list) {
319 if !item.try_visit_columns(visitor)? {
320 return Ok(false);
321 }
322 }
323 Ok(true)
324 }
325 Self::Case {
326 base,
327 when,
328 else_branch,
329 } => {
330 for expression in base
331 .as_deref()
332 .into_iter()
333 .chain(
334 when.iter()
335 .flat_map(|(condition, result)| [condition, result]),
336 )
337 .chain(else_branch.as_deref())
338 {
339 if !expression.try_visit_columns(visitor)? {
340 return Ok(false);
341 }
342 }
343 Ok(true)
344 }
345 Self::Default
346 | Self::Star
347 | Self::QualifiedStar(_)
348 | Self::Position(_)
349 | Self::WindowCall { .. }
350 | Self::ScalarSubquery(_)
351 | Self::Exists { .. }
352 | Self::InSubquery { .. } => Ok(false),
353 }
354 }
355
356 #[must_use]
357 pub fn contains_window(&self) -> bool {
358 match self {
359 Self::WindowCall { .. } => true,
360 Self::Func {
361 args,
362 order_by,
363 filter,
364 ..
365 } => {
366 args.iter().any(Self::contains_window)
367 || order_by.iter().any(|order| order.expr.contains_window())
368 || filter.as_deref().is_some_and(Self::contains_window)
369 }
370 Self::Array(items)
371 | Self::Row(items)
372 | Self::CompositeRow { items, .. }
373 | Self::And(items)
374 | Self::Or(items) => items.iter().any(Self::contains_window),
375 Self::Binary { lhs, rhs, .. } => lhs.contains_window() || rhs.contains_window(),
376 Self::UnaryMinus(expr)
377 | Self::Not(expr)
378 | Self::IsNull { expr, .. }
379 | Self::Cast { expr, .. }
380 | Self::Collated { expr, .. }
381 | Self::InSubquery { expr, .. } => expr.contains_window(),
382 Self::Between { expr, low, high } => {
383 expr.contains_window() || low.contains_window() || high.contains_window()
384 }
385 Self::InList { expr, list, .. } => {
386 expr.contains_window() || list.iter().any(Self::contains_window)
387 }
388 Self::Case {
389 base,
390 when,
391 else_branch,
392 } => {
393 base.as_deref().is_some_and(Self::contains_window)
394 || when.iter().any(|(condition, result)| {
395 condition.contains_window() || result.contains_window()
396 })
397 || else_branch.as_deref().is_some_and(Self::contains_window)
398 }
399 Self::Default
400 | Self::Star
401 | Self::QualifiedStar(_)
402 | Self::Column(_)
403 | Self::QualifiedColumn { .. }
404 | Self::Position(_)
405 | Self::InternalColumn(_)
406 | Self::Literal(_)
407 | Self::TypedLiteral { .. }
408 | Self::Param(_)
409 | Self::ScalarSubquery(_)
410 | Self::Exists { .. } => false,
411 }
412 }
413
414 #[must_use]
415 pub fn contains_subquery(&self) -> bool {
416 match self {
417 Self::ScalarSubquery(_) | Self::Exists { .. } | Self::InSubquery { .. } => true,
418 Self::Func {
419 args,
420 order_by,
421 filter,
422 ..
423 } => {
424 args.iter().any(Self::contains_subquery)
425 || order_by.iter().any(|order| order.expr.contains_subquery())
426 || filter.as_deref().is_some_and(Self::contains_subquery)
427 }
428 Self::Array(items)
429 | Self::Row(items)
430 | Self::CompositeRow { items, .. }
431 | Self::And(items)
432 | Self::Or(items) => items.iter().any(Self::contains_subquery),
433 Self::Binary { lhs, rhs, .. } => lhs.contains_subquery() || rhs.contains_subquery(),
434 Self::UnaryMinus(expr)
435 | Self::Not(expr)
436 | Self::IsNull { expr, .. }
437 | Self::Cast { expr, .. }
438 | Self::Collated { expr, .. } => expr.contains_subquery(),
439 Self::Between { expr, low, high } => {
440 expr.contains_subquery() || low.contains_subquery() || high.contains_subquery()
441 }
442 Self::InList { expr, list, .. } => {
443 expr.contains_subquery() || list.iter().any(Self::contains_subquery)
444 }
445 Self::WindowCall {
446 args, spec, filter, ..
447 } => {
448 args.iter().any(Self::contains_subquery)
449 || filter.as_deref().is_some_and(Self::contains_subquery)
450 || spec.partition_by.iter().any(Self::contains_subquery)
451 || spec
452 .order_by
453 .iter()
454 .any(|order| order.expr.contains_subquery())
455 || spec.frame.as_ref().is_some_and(|frame| {
456 frame_has(&frame.start, Self::contains_subquery)
457 || frame_has(&frame.end, Self::contains_subquery)
458 })
459 }
460 Self::Case {
461 base,
462 when,
463 else_branch,
464 } => {
465 base.as_deref().is_some_and(Self::contains_subquery)
466 || when.iter().any(|(condition, result)| {
467 condition.contains_subquery() || result.contains_subquery()
468 })
469 || else_branch.as_deref().is_some_and(Self::contains_subquery)
470 }
471 Self::Default
472 | Self::Star
473 | Self::QualifiedStar(_)
474 | Self::Column(_)
475 | Self::QualifiedColumn { .. }
476 | Self::Position(_)
477 | Self::InternalColumn(_)
478 | Self::Literal(_)
479 | Self::TypedLiteral { .. }
480 | Self::Param(_) => false,
481 }
482 }
483
484 #[must_use]
485 pub fn contains_parameter(&self) -> bool {
486 match self {
487 Self::Param(_) => true,
488 Self::Func {
489 args,
490 order_by,
491 filter,
492 ..
493 } => {
494 args.iter().any(Self::contains_parameter)
495 || order_by.iter().any(|order| order.expr.contains_parameter())
496 || filter.as_deref().is_some_and(Self::contains_parameter)
497 }
498 Self::Array(items)
499 | Self::Row(items)
500 | Self::CompositeRow { items, .. }
501 | Self::And(items)
502 | Self::Or(items) => items.iter().any(Self::contains_parameter),
503 Self::Binary { lhs, rhs, .. } => lhs.contains_parameter() || rhs.contains_parameter(),
504 Self::UnaryMinus(expr)
505 | Self::Not(expr)
506 | Self::IsNull { expr, .. }
507 | Self::Cast { expr, .. }
508 | Self::Collated { expr, .. }
509 | Self::InSubquery { expr, .. } => expr.contains_parameter(),
510 Self::Between { expr, low, high } => {
511 expr.contains_parameter() || low.contains_parameter() || high.contains_parameter()
512 }
513 Self::InList { expr, list, .. } => {
514 expr.contains_parameter() || list.iter().any(Self::contains_parameter)
515 }
516 Self::WindowCall {
517 args, spec, filter, ..
518 } => {
519 args.iter().any(Self::contains_parameter)
520 || filter.as_deref().is_some_and(Self::contains_parameter)
521 || spec.partition_by.iter().any(Self::contains_parameter)
522 || spec
523 .order_by
524 .iter()
525 .any(|order| order.expr.contains_parameter())
526 || spec.frame.as_ref().is_some_and(|frame| {
527 frame_has(&frame.start, Self::contains_parameter)
528 || frame_has(&frame.end, Self::contains_parameter)
529 })
530 }
531 Self::Case {
532 base,
533 when,
534 else_branch,
535 } => {
536 base.as_deref().is_some_and(Self::contains_parameter)
537 || when.iter().any(|(condition, result)| {
538 condition.contains_parameter() || result.contains_parameter()
539 })
540 || else_branch.as_deref().is_some_and(Self::contains_parameter)
541 }
542 Self::Default
543 | Self::Star
544 | Self::QualifiedStar(_)
545 | Self::Column(_)
546 | Self::QualifiedColumn { .. }
547 | Self::Position(_)
548 | Self::InternalColumn(_)
549 | Self::Literal(_)
550 | Self::TypedLiteral { .. }
551 | Self::ScalarSubquery(_)
552 | Self::Exists { .. } => false,
553 }
554 }
555
556 #[must_use]
557 pub fn contains_aggregate(&self, is_aggregate: &dyn Fn(&str) -> bool) -> bool {
558 match self {
559 Self::Func {
560 name,
561 args,
562 order_by,
563 filter,
564 ..
565 } => {
566 is_aggregate(name)
567 || args
568 .iter()
569 .any(|expression| expression.contains_aggregate(is_aggregate))
570 || order_by
571 .iter()
572 .any(|order| order.expr.contains_aggregate(is_aggregate))
573 || filter
574 .as_deref()
575 .is_some_and(|expression| expression.contains_aggregate(is_aggregate))
576 }
577 Self::Array(items)
578 | Self::Row(items)
579 | Self::CompositeRow { items, .. }
580 | Self::And(items)
581 | Self::Or(items) => items
582 .iter()
583 .any(|expression| expression.contains_aggregate(is_aggregate)),
584 Self::Binary { lhs, rhs, .. } => {
585 lhs.contains_aggregate(is_aggregate) || rhs.contains_aggregate(is_aggregate)
586 }
587 Self::UnaryMinus(expr)
588 | Self::Not(expr)
589 | Self::IsNull { expr, .. }
590 | Self::Cast { expr, .. }
591 | Self::Collated { expr, .. }
592 | Self::InSubquery { expr, .. } => expr.contains_aggregate(is_aggregate),
593 Self::Between { expr, low, high } => {
594 expr.contains_aggregate(is_aggregate)
595 || low.contains_aggregate(is_aggregate)
596 || high.contains_aggregate(is_aggregate)
597 }
598 Self::InList { expr, list, .. } => {
599 expr.contains_aggregate(is_aggregate)
600 || list
601 .iter()
602 .any(|item| item.contains_aggregate(is_aggregate))
603 }
604 Self::Case {
605 base,
606 when,
607 else_branch,
608 } => {
609 base.as_deref()
610 .is_some_and(|expression| expression.contains_aggregate(is_aggregate))
611 || when.iter().any(|(condition, result)| {
612 condition.contains_aggregate(is_aggregate)
613 || result.contains_aggregate(is_aggregate)
614 })
615 || else_branch
616 .as_deref()
617 .is_some_and(|expression| expression.contains_aggregate(is_aggregate))
618 }
619 Self::Default
620 | Self::Star
621 | Self::QualifiedStar(_)
622 | Self::Column(_)
623 | Self::QualifiedColumn { .. }
624 | Self::Position(_)
625 | Self::InternalColumn(_)
626 | Self::Literal(_)
627 | Self::TypedLiteral { .. }
628 | Self::Param(_)
629 | Self::ScalarSubquery(_)
630 | Self::Exists { .. }
631 | Self::WindowCall { .. } => false,
632 }
633 }
634}
635
636fn frame_has(bound: &ScalarFrameBound, predicate: fn(&ScalarExpr) -> bool) -> bool {
637 match bound {
638 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
639 predicate(expression)
640 }
641 ScalarFrameBound::UnboundedPreceding
642 | ScalarFrameBound::UnboundedFollowing
643 | ScalarFrameBound::CurrentRow => false,
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::{ScalarExpr, ScalarFrameBound};
650 use crate::ast::{FrameExclusion, FrameMode};
651 use uqa_core::Value;
652
653 #[test]
654 fn visit_includes_root_and_nested_expressions() {
655 let expression = ScalarExpr::Binary {
656 op: crate::ast::BinaryOp::Add,
657 lhs: Box::new(ScalarExpr::Column("amount".into())),
658 rhs: Box::new(ScalarExpr::Literal(Value::Int(1))),
659 };
660 let mut visited = Vec::new();
661 expression.visit(&mut |part| visited.push(part.clone()));
662 assert_eq!(visited.len(), 3);
663 assert_eq!(visited[0], expression);
664 }
665
666 #[test]
667 fn fallible_visits_skip_selected_subtrees_and_stop_before_later_siblings() {
668 let expression = ScalarExpr::Row(vec![
669 ScalarExpr::Array(vec![ScalarExpr::Column("hidden".into())]),
670 ScalarExpr::Column("reject".into()),
671 ScalarExpr::Column("unvisited".into()),
672 ]);
673 let mut visited = Vec::new();
674 let result = expression.try_visit(&mut |part| {
675 visited.push(part.clone());
676 match part {
677 ScalarExpr::Array(_) => Ok(false),
678 ScalarExpr::Column(name) if name == "reject" => Err("grouping"),
679 _ => Ok(true),
680 }
681 });
682 assert_eq!(result, Err("grouping"));
683 assert_eq!(visited.len(), 3);
684 assert!(matches!(&visited[2], ScalarExpr::Column(name) if name == "reject"));
685 }
686
687 #[test]
688 fn traversal_includes_window_frame_expressions() {
689 let expression = ScalarExpr::WindowCall {
690 name: "sum".into(),
691 args: vec![ScalarExpr::Column("amount".into())],
692 spec: super::super::ScalarWindowSpec {
693 definition: None,
694 partition_by: vec![ScalarExpr::QualifiedColumn {
695 qualifier: "orders".into(),
696 column: "account_id".into(),
697 }],
698 order_by: Vec::new(),
699 frame: Some(super::super::ScalarWindowFrame {
700 mode: FrameMode::Rows,
701 start: ScalarFrameBound::Preceding(Box::new(ScalarExpr::Param(0))),
702 end: ScalarFrameBound::CurrentRow,
703 between: true,
704 exclusion: FrameExclusion::NoOthers,
705 }),
706 },
707 filter: None,
708 modifiers: crate::ast::WindowCallModifiers::default(),
709 };
710 let mut visited_parameter = false;
711 expression.visit(&mut |part| {
712 visited_parameter |= matches!(part, ScalarExpr::Param(0));
713 });
714 assert!(visited_parameter);
715 assert!(expression.contains_window());
716 assert!(expression.contains_parameter());
717 }
718
719 #[test]
720 fn mutable_visits_rewrite_nested_expressions() {
721 let literal = || ScalarExpr::Literal(Value::Str("t".into()));
722 let mut expression = ScalarExpr::Cast {
723 implicit: false,
724 expr: Box::new(ScalarExpr::Case {
725 base: None,
726 when: vec![(
727 literal(),
728 ScalarExpr::Cast {
729 implicit: false,
730 expr: Box::new(literal()),
731 ty: "regclass".into(),
732 },
733 )],
734 else_branch: Some(Box::new(ScalarExpr::WindowCall {
735 name: "sum".into(),
736 args: vec![literal()],
737 spec: super::super::ScalarWindowSpec {
738 definition: None,
739 partition_by: Vec::new(),
740 order_by: Vec::new(),
741 frame: Some(super::super::ScalarWindowFrame {
742 mode: FrameMode::Rows,
743 start: ScalarFrameBound::Preceding(Box::new(literal())),
744 end: ScalarFrameBound::CurrentRow,
745 between: true,
746 exclusion: FrameExclusion::NoOthers,
747 }),
748 },
749 filter: Some(Box::new(literal())),
750 modifiers: crate::ast::WindowCallModifiers::default(),
751 })),
752 }),
753 ty: "text".into(),
754 };
755 let mut rewritten = 0;
756 expression.visit_mut(&mut |part| {
757 if matches!(part, ScalarExpr::Literal(Value::Str(_))) {
758 *part = ScalarExpr::Literal(Value::Int(1));
759 rewritten += 1;
760 }
761 });
762 assert_eq!(rewritten, 5);
763 let mut remaining = 0;
764 expression.visit(&mut |part| {
765 remaining += usize::from(matches!(part, ScalarExpr::Literal(Value::Str(_))));
766 });
767 assert_eq!(remaining, 0);
768 }
769
770 #[test]
771 fn owned_walkers_preserve_column_and_aggregate_policy() {
772 let expression = ScalarExpr::Func {
773 order_syntax: crate::ast::FunctionOrderSyntax::Ordinary,
774 name: "sum".into(),
775 binding: None,
776 args: vec![ScalarExpr::QualifiedColumn {
777 qualifier: "orders".into(),
778 column: "amount".into(),
779 }],
780 distinct: false,
781 order_by: Vec::new(),
782 filter: None,
783 };
784 let mut columns = std::collections::BTreeSet::new();
785 assert!(expression.collect_columns(&mut columns));
786 assert_eq!(columns, std::collections::BTreeSet::from(["amount".into()]));
787 assert!(expression.contains_aggregate(&|name| name == "sum"));
788 assert!(!expression.contains_subquery());
789 }
790
791 #[test]
792 fn borrowed_column_visits_keep_names_and_stop_at_the_first_failure() {
793 let expression = ScalarExpr::Row(vec![
794 ScalarExpr::Column("first".into()),
795 ScalarExpr::QualifiedColumn {
796 qualifier: "table".into(),
797 column: "second".into(),
798 },
799 ScalarExpr::Column("first".into()),
800 ]);
801 let mut borrowed = Vec::new();
802 assert!(expression
803 .try_visit_columns(&mut |name| {
804 borrowed.push(name);
805 Ok::<_, &str>(())
806 })
807 .unwrap());
808 assert_eq!(borrowed, ["first", "second", "first"]);
809 let ScalarExpr::Row(items) = &expression else {
810 unreachable!()
811 };
812 let ScalarExpr::Column(first) = &items[0] else {
813 unreachable!()
814 };
815 assert_eq!(borrowed[0].as_ptr(), first.as_ptr());
816 let mut visits = 0;
817 let result = expression.try_visit_columns(&mut |_| {
818 visits += 1;
819 if visits == 2 {
820 Err("quota")
821 } else {
822 Ok(())
823 }
824 });
825 assert_eq!(result, Err("quota"));
826 assert_eq!(visits, 2);
827 }
828
829 #[test]
830 fn borrowed_column_visits_preserve_unprojectable_prefix_semantics() {
831 let expression = ScalarExpr::Array(vec![
832 ScalarExpr::Column("before".into()),
833 ScalarExpr::Position(0),
834 ScalarExpr::Column("after".into()),
835 ]);
836 let mut borrowed = Vec::new();
837 assert!(!expression
838 .try_visit_columns(&mut |name| {
839 borrowed.push(name);
840 Ok::<_, &str>(())
841 })
842 .unwrap());
843 let mut owned = std::collections::BTreeSet::new();
844 assert!(!expression.collect_columns(&mut owned));
845 assert_eq!(borrowed, ["before"]);
846 assert_eq!(owned, std::collections::BTreeSet::from(["before".into()]));
847 }
848}