1use std::any::Any;
19use std::fmt::Display;
20use std::ops::Deref;
21use std::sync::Arc;
22use std::vec::IntoIter;
23
24use super::ProjectionMapping;
25use crate::expressions::Literal;
26use crate::physical_expr::add_offset_to_expr;
27use crate::projection::ProjectionTargets;
28use crate::{PhysicalExpr, PhysicalExprRef, PhysicalSortExpr, PhysicalSortRequirement};
29
30use datafusion_common::tree_node::{Transformed, TransformedResult, TreeNode};
31use datafusion_common::{JoinType, Result, ScalarValue};
32use datafusion_physical_expr_common::physical_expr::format_physical_expr_list;
33
34use indexmap::{IndexMap, IndexSet};
35
36#[derive(Clone, Debug, Default, Eq, PartialEq)]
43pub enum AcrossPartitions {
44 #[default]
45 Heterogeneous,
46 Uniform(Option<ScalarValue>),
47}
48
49impl Display for AcrossPartitions {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 match self {
52 AcrossPartitions::Heterogeneous => write!(f, "(heterogeneous)"),
53 AcrossPartitions::Uniform(value) => {
54 if let Some(val) = value {
55 write!(f, "(uniform: {val})")
56 } else {
57 write!(f, "(uniform: unknown)")
58 }
59 }
60 }
61 }
62}
63
64#[derive(Clone, Debug)]
89pub struct ConstExpr {
90 pub expr: Arc<dyn PhysicalExpr>,
92 pub across_partitions: AcrossPartitions,
94}
95impl ConstExpr {
101 pub fn new(expr: Arc<dyn PhysicalExpr>, across_partitions: AcrossPartitions) -> Self {
109 let mut result = ConstExpr::from(expr);
110 if result.across_partitions == AcrossPartitions::Heterogeneous {
113 result.across_partitions = across_partitions;
114 }
115 result
116 }
117
118 pub fn format_list(input: &[ConstExpr]) -> impl Display + '_ {
120 struct DisplayableList<'a>(&'a [ConstExpr]);
121 impl Display for DisplayableList<'_> {
122 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
123 let mut first = true;
124 for const_expr in self.0 {
125 if first {
126 first = false;
127 } else {
128 write!(f, ",")?;
129 }
130 write!(f, "{const_expr}")?;
131 }
132 Ok(())
133 }
134 }
135 DisplayableList(input)
136 }
137}
138
139impl PartialEq for ConstExpr {
140 fn eq(&self, other: &Self) -> bool {
141 self.across_partitions == other.across_partitions && self.expr.eq(&other.expr)
142 }
143}
144
145impl Display for ConstExpr {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 write!(f, "{}", self.expr)?;
148 write!(f, "{}", self.across_partitions)
149 }
150}
151
152impl From<Arc<dyn PhysicalExpr>> for ConstExpr {
153 fn from(expr: Arc<dyn PhysicalExpr>) -> Self {
154 let across = if let Some(lit) = expr.downcast_ref::<Literal>() {
158 AcrossPartitions::Uniform(Some(lit.value().clone()))
159 } else {
160 AcrossPartitions::Heterogeneous
161 };
162 Self {
163 expr,
164 across_partitions: across,
165 }
166 }
167}
168
169#[derive(Clone, Debug, Default, Eq, PartialEq)]
177pub struct EquivalenceClass {
178 pub(crate) exprs: IndexSet<Arc<dyn PhysicalExpr>>,
181 pub(crate) constant: Option<AcrossPartitions>,
184}
185
186impl EquivalenceClass {
187 pub fn new(exprs: impl IntoIterator<Item = Arc<dyn PhysicalExpr>>) -> Self {
189 let mut class = Self::default();
190 for expr in exprs {
191 class.push(expr);
192 }
193 class
194 }
195
196 pub fn canonical_expr(&self) -> Option<&Arc<dyn PhysicalExpr>> {
199 self.exprs.iter().next()
200 }
201
202 pub fn push(&mut self, expr: Arc<dyn PhysicalExpr>) {
205 if let Some(lit) = expr.downcast_ref::<Literal>() {
206 let expr_across = AcrossPartitions::Uniform(Some(lit.value().clone()));
207 if let Some(across) = self.constant.as_mut() {
208 if *across == AcrossPartitions::Heterogeneous {
210 *across = expr_across;
211 }
212 } else {
213 self.constant = Some(expr_across);
214 }
215 }
216 self.exprs.insert(expr);
217 }
218
219 pub fn extend(&mut self, other: Self) {
221 self.exprs.extend(other.exprs);
222 match (&self.constant, &other.constant) {
223 (Some(across), Some(_)) => {
224 if across == &AcrossPartitions::Heterogeneous {
226 self.constant = other.constant;
227 }
228 }
229 (None, Some(_)) => self.constant = other.constant,
230 (_, None) => {}
231 }
232 }
233
234 pub fn contains_any(&self, other: &Self) -> bool {
237 self.exprs.intersection(&other.exprs).next().is_some()
238 }
239
240 pub fn is_trivial(&self) -> bool {
244 self.exprs.is_empty() || (self.exprs.len() == 1 && self.constant.is_none())
245 }
246
247 pub fn try_with_offset(&self, offset: isize) -> Result<Self> {
250 let mut cls = Self::default();
251 for expr_result in self
252 .exprs
253 .iter()
254 .cloned()
255 .map(|e| add_offset_to_expr(e, offset))
256 {
257 cls.push(expr_result?);
258 }
259 Ok(cls)
260 }
261}
262
263impl Deref for EquivalenceClass {
264 type Target = IndexSet<Arc<dyn PhysicalExpr>>;
265
266 fn deref(&self) -> &Self::Target {
267 &self.exprs
268 }
269}
270
271impl IntoIterator for EquivalenceClass {
272 type Item = Arc<dyn PhysicalExpr>;
273 type IntoIter = <IndexSet<Self::Item> as IntoIterator>::IntoIter;
274
275 fn into_iter(self) -> Self::IntoIter {
276 self.exprs.into_iter()
277 }
278}
279
280impl Display for EquivalenceClass {
281 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
282 write!(f, "{{")?;
283 write!(f, "members: {}", format_physical_expr_list(&self.exprs))?;
284 if let Some(across) = &self.constant {
285 write!(f, ", constant: {across}")?;
286 }
287 write!(f, "}}")
288 }
289}
290
291impl From<EquivalenceClass> for Vec<Arc<dyn PhysicalExpr>> {
292 fn from(cls: EquivalenceClass) -> Self {
293 cls.exprs.into_iter().collect()
294 }
295}
296
297type AugmentedMapping<'a> = IndexMap<
298 &'a Arc<dyn PhysicalExpr>,
299 (&'a ProjectionTargets, Option<&'a EquivalenceClass>),
300>;
301
302#[derive(Clone, Debug, Default)]
305pub struct EquivalenceGroup {
306 map: IndexMap<Arc<dyn PhysicalExpr>, usize>,
308 classes: Vec<EquivalenceClass>,
310}
311
312impl EquivalenceGroup {
313 pub fn new(classes: impl IntoIterator<Item = EquivalenceClass>) -> Self {
315 classes.into_iter().collect::<Vec<_>>().into()
316 }
317
318 pub fn add_constant(&mut self, const_expr: ConstExpr) {
320 if let Some(idx) = self.map.get(&const_expr.expr) {
323 let cls = &mut self.classes[*idx];
324 if let Some(across) = cls.constant.as_mut() {
325 if *across == AcrossPartitions::Heterogeneous {
327 *across = const_expr.across_partitions;
328 }
329 } else {
330 cls.constant = Some(const_expr.across_partitions);
331 }
332 return;
333 }
334 if let AcrossPartitions::Uniform(_) = &const_expr.across_partitions {
337 for (idx, cls) in self.classes.iter_mut().enumerate() {
338 if cls
339 .constant
340 .as_ref()
341 .is_some_and(|across| const_expr.across_partitions.eq(across))
342 {
343 self.map.insert(Arc::clone(&const_expr.expr), idx);
344 cls.push(const_expr.expr);
345 return;
346 }
347 }
348 }
349 let mut new_class = EquivalenceClass::new(std::iter::once(const_expr.expr));
351 if new_class.constant.is_none() {
352 new_class.constant = Some(const_expr.across_partitions);
353 }
354 Self::update_lookup_table(&mut self.map, &new_class, self.classes.len());
355 self.classes.push(new_class);
356 }
357
358 pub fn clear_per_partition_constants(&mut self) -> bool {
362 let (mut idx, mut change) = (0, false);
363 while idx < self.classes.len() {
364 let cls = &mut self.classes[idx];
365 if let Some(AcrossPartitions::Heterogeneous) = cls.constant {
366 change = true;
367 if cls.len() == 1 {
368 self.remove_class_at_idx(idx);
370 continue;
371 } else {
372 cls.constant = None;
373 }
374 }
375 idx += 1;
376 }
377 change
378 }
379
380 pub fn add_equal_conditions(
385 &mut self,
386 left: Arc<dyn PhysicalExpr>,
387 right: Arc<dyn PhysicalExpr>,
388 ) -> bool {
389 let first_class = self.map.get(&left).copied();
390 let second_class = self.map.get(&right).copied();
391 match (first_class, second_class) {
392 (Some(mut first_idx), Some(mut second_idx)) => {
393 match first_idx.cmp(&second_idx) {
396 std::cmp::Ordering::Equal => return false,
398 std::cmp::Ordering::Greater => {
400 std::mem::swap(&mut first_idx, &mut second_idx);
401 }
402 _ => {}
403 }
404 let other_class = self.remove_class_at_idx(second_idx);
408 Self::update_lookup_table(&mut self.map, &other_class, first_idx);
410 self.classes[first_idx].extend(other_class);
411 }
412 (Some(group_idx), None) => {
413 self.map.insert(Arc::clone(&right), group_idx);
415 self.classes[group_idx].push(right);
416 }
417 (None, Some(group_idx)) => {
418 self.map.insert(Arc::clone(&left), group_idx);
420 self.classes[group_idx].push(left);
421 }
422 (None, None) => {
423 let class = EquivalenceClass::new([left, right]);
426 Self::update_lookup_table(&mut self.map, &class, self.classes.len());
427 self.classes.push(class);
428 return true;
429 }
430 }
431 false
432 }
433
434 fn remove_class_at_idx(&mut self, idx: usize) -> EquivalenceClass {
436 let cls = self.classes.swap_remove(idx);
438 for expr in cls.iter() {
440 self.map.swap_remove(expr);
441 }
442 if idx < self.classes.len() {
444 Self::update_lookup_table(&mut self.map, &self.classes[idx], idx);
445 }
446 cls
447 }
448
449 fn update_lookup_table(
452 map: &mut IndexMap<Arc<dyn PhysicalExpr>, usize>,
453 cls: &EquivalenceClass,
454 idx: usize,
455 ) {
456 for expr in cls.iter() {
457 map.insert(Arc::clone(expr), idx);
458 }
459 }
460
461 fn remove_redundant_entries(&mut self) -> bool {
464 let mut change = false;
466 for idx in (0..self.classes.len()).rev() {
467 if self.classes[idx].is_trivial() {
468 self.remove_class_at_idx(idx);
469 change = true;
470 }
471 }
472 self.bridge_classes() || change
474 }
475
476 fn bridge_classes(&mut self) -> bool {
482 let (mut idx, mut change) = (0, false);
483 'scan: while idx < self.classes.len() {
484 for other_idx in (idx + 1..self.classes.len()).rev() {
485 if self.classes[idx].contains_any(&self.classes[other_idx]) {
486 let extension = self.remove_class_at_idx(other_idx);
487 Self::update_lookup_table(&mut self.map, &extension, idx);
488 self.classes[idx].extend(extension);
489 change = true;
490 continue 'scan;
491 }
492 }
493 idx += 1;
494 }
495 change
496 }
497
498 pub fn extend(&mut self, other: Self) -> bool {
502 for (idx, cls) in other.classes.iter().enumerate() {
503 Self::update_lookup_table(&mut self.map, cls, idx);
505 }
506 self.classes.extend(other.classes);
507 self.bridge_classes()
508 }
509
510 pub fn normalize_expr(&self, expr: Arc<dyn PhysicalExpr>) -> Arc<dyn PhysicalExpr> {
514 expr.transform(|expr| {
515 let cls = self.get_equivalence_class(&expr);
516 let Some(canonical) = cls.and_then(|cls| cls.canonical_expr()) else {
517 return Ok(Transformed::no(expr));
518 };
519 Ok(Transformed::yes(Arc::clone(canonical)))
520 })
521 .data()
522 .unwrap()
523 }
525
526 pub fn normalize_sort_expr(
532 &self,
533 mut sort_expr: PhysicalSortExpr,
534 ) -> PhysicalSortExpr {
535 sort_expr.expr = self.normalize_expr(sort_expr.expr);
536 sort_expr
537 }
538
539 pub fn normalize_sort_exprs<'a>(
548 &'a self,
549 sort_exprs: impl IntoIterator<Item = PhysicalSortExpr> + 'a,
550 ) -> impl Iterator<Item = PhysicalSortExpr> + 'a {
551 sort_exprs
552 .into_iter()
553 .map(|sort_expr| self.normalize_sort_expr(sort_expr))
554 .filter(|sort_expr| !self.is_uniform_constant(&sort_expr.expr))
555 }
556
557 fn is_uniform_constant(&self, expr: &Arc<dyn PhysicalExpr>) -> bool {
563 matches!(
564 self.is_expr_constant(expr),
565 Some(AcrossPartitions::Uniform(_))
566 )
567 }
568
569 pub fn normalize_sort_requirement(
575 &self,
576 mut sort_requirement: PhysicalSortRequirement,
577 ) -> PhysicalSortRequirement {
578 sort_requirement.expr = self.normalize_expr(sort_requirement.expr);
579 sort_requirement
580 }
581
582 pub fn normalize_sort_requirements<'a>(
591 &'a self,
592 sort_reqs: impl IntoIterator<Item = PhysicalSortRequirement> + 'a,
593 ) -> impl Iterator<Item = PhysicalSortRequirement> + 'a {
594 sort_reqs
595 .into_iter()
596 .map(|req| self.normalize_sort_requirement(req))
597 .filter(|req| !self.is_uniform_constant(&req.expr))
598 }
599
600 fn project_expr_indirect(
603 aug_mapping: &AugmentedMapping,
604 expr: &Arc<dyn PhysicalExpr>,
605 ) -> Option<Arc<dyn PhysicalExpr>> {
606 if expr.downcast_ref::<Literal>().is_some() {
608 return Some(Arc::clone(expr));
609 }
610
611 for (targets, eq_class) in aug_mapping.values() {
614 if eq_class.as_ref().is_some_and(|cls| cls.contains(expr)) {
619 let (target, _) = targets.first();
620 return Some(Arc::clone(target));
621 }
622 }
623 let children = expr.children();
625 if children.is_empty() {
626 return None;
628 }
629 children
630 .into_iter()
631 .map(|child| {
632 if let Some((targets, _)) = aug_mapping.get(child) {
635 let (target, _) = targets.first();
637 Some(Arc::clone(target))
638 } else {
639 Self::project_expr_indirect(aug_mapping, child)
640 }
641 })
642 .collect::<Option<Vec<_>>>()
643 .map(|children| Arc::clone(expr).with_new_children(children).unwrap())
644 }
645
646 fn augment_projection_mapping<'a>(
647 &'a self,
648 mapping: &'a ProjectionMapping,
649 ) -> AugmentedMapping<'a> {
650 mapping
651 .iter()
652 .map(|(k, v)| {
653 let eq_class = self.get_equivalence_class(k);
654 (k, (v, eq_class))
655 })
656 .collect()
657 }
658
659 pub fn project_expr(
662 &self,
663 mapping: &ProjectionMapping,
664 expr: &Arc<dyn PhysicalExpr>,
665 ) -> Option<Arc<dyn PhysicalExpr>> {
666 if let Some(targets) = mapping.get(expr) {
667 let (target, _) = targets.first();
669 Some(Arc::clone(target))
670 } else {
671 let aug_mapping = self.augment_projection_mapping(mapping);
672 Self::project_expr_indirect(&aug_mapping, expr)
673 }
674 }
675
676 pub fn project_expressions<'a>(
681 &'a self,
682 mapping: &'a ProjectionMapping,
683 expressions: impl IntoIterator<Item = &'a Arc<dyn PhysicalExpr>> + 'a,
684 ) -> impl Iterator<Item = Option<Arc<dyn PhysicalExpr>>> + 'a {
685 let mut aug_mapping = None;
686 expressions.into_iter().map(move |expr| {
687 if let Some(targets) = mapping.get(expr) {
688 let (target, _) = targets.first();
690 Some(Arc::clone(target))
691 } else {
692 let aug_mapping = aug_mapping
693 .get_or_insert_with(|| self.augment_projection_mapping(mapping));
694 Self::project_expr_indirect(aug_mapping, expr)
695 }
696 })
697 }
698
699 pub fn project(&self, mapping: &ProjectionMapping) -> Self {
701 let projected_classes = self.iter().map(|cls| {
702 let new_exprs = self.project_expressions(mapping, cls.iter());
703 EquivalenceClass::new(new_exprs.flatten())
704 });
705
706 let mut new_constants = vec![];
709 let mut new_classes = IndexMap::<_, EquivalenceClass>::new();
710 for (source, targets) in mapping.iter() {
711 let normalized_expr = self.normalize_expr(Arc::clone(source));
717 let cls = new_classes.entry(normalized_expr).or_default();
718 for (target, _) in targets.iter() {
719 cls.push(Arc::clone(target));
720 }
721 if let Some(across) = self.is_expr_constant(source) {
723 for (target, _) in targets.iter() {
724 let const_expr = ConstExpr::new(Arc::clone(target), across.clone());
725 new_constants.push(const_expr);
726 }
727 }
728 }
729
730 let classes = projected_classes
732 .chain(new_classes.into_values())
733 .filter(|cls| !cls.is_trivial());
734 let mut result = Self::new(classes);
735 for constant in new_constants {
737 result.add_constant(constant);
738 }
739 result
740 }
741
742 pub fn is_expr_constant(
747 &self,
748 expr: &Arc<dyn PhysicalExpr>,
749 ) -> Option<AcrossPartitions> {
750 if let Some(lit) = expr.downcast_ref::<Literal>() {
751 return Some(AcrossPartitions::Uniform(Some(lit.value().clone())));
752 }
753 if let Some(cls) = self.get_equivalence_class(expr)
754 && cls.constant.is_some()
755 {
756 return cls.constant.clone();
757 }
758 let children = expr.children();
762 if children.is_empty() {
763 return None;
764 }
765 for child in children {
766 self.is_expr_constant(child)?;
767 }
768 Some(AcrossPartitions::Heterogeneous)
769 }
770
771 pub fn get_equivalence_class(
774 &self,
775 expr: &Arc<dyn PhysicalExpr>,
776 ) -> Option<&EquivalenceClass> {
777 self.map.get(expr).map(|idx| &self.classes[*idx])
778 }
779
780 pub fn join(
782 &self,
783 right_equivalences: &Self,
784 join_type: &JoinType,
785 left_size: usize,
786 on: &[(PhysicalExprRef, PhysicalExprRef)],
787 ) -> Result<Self> {
788 let group = match join_type {
789 JoinType::Inner | JoinType::Left | JoinType::Full | JoinType::Right => {
790 let mut result = Self::new(
791 self.iter().cloned().chain(
792 right_equivalences
793 .iter()
794 .map(|cls| cls.try_with_offset(left_size as _))
795 .collect::<Result<Vec<_>>>()?,
796 ),
797 );
798 if join_type == &JoinType::Inner {
801 for (lhs, rhs) in on.iter() {
802 let new_lhs = Arc::clone(lhs);
803 let new_rhs =
805 add_offset_to_expr(Arc::clone(rhs), left_size as _)?;
806 result.add_equal_conditions(new_lhs, new_rhs);
807 }
808 }
809 result
810 }
811 JoinType::LeftSemi | JoinType::LeftAnti | JoinType::LeftMark => self.clone(),
812 JoinType::RightSemi | JoinType::RightAnti | JoinType::RightMark => {
813 right_equivalences.clone()
814 }
815 };
816 Ok(group)
817 }
818
819 pub fn exprs_equal(
824 &self,
825 left: &Arc<dyn PhysicalExpr>,
826 right: &Arc<dyn PhysicalExpr>,
827 ) -> bool {
828 if left.eq(right) {
830 return true;
831 }
832
833 if let Some(left_class) = self.get_equivalence_class(left)
836 && left_class.contains(right)
837 {
838 return true;
839 }
840 if let Some(right_class) = self.get_equivalence_class(right)
841 && right_class.contains(left)
842 {
843 return true;
844 }
845
846 let left_children = left.children();
848 let right_children = right.children();
849
850 if left_children.is_empty() || right_children.is_empty() {
853 return false;
854 }
855
856 if (left as &dyn Any).type_id() != (right as &dyn Any).type_id() {
858 return false;
859 }
860
861 if left_children.len() != right_children.len() {
863 return false;
864 }
865
866 left_children
868 .into_iter()
869 .zip(right_children)
870 .all(|(left_child, right_child)| self.exprs_equal(left_child, right_child))
871 }
872}
873
874impl Deref for EquivalenceGroup {
875 type Target = [EquivalenceClass];
876
877 fn deref(&self) -> &Self::Target {
878 &self.classes
879 }
880}
881
882impl IntoIterator for EquivalenceGroup {
883 type Item = EquivalenceClass;
884 type IntoIter = IntoIter<Self::Item>;
885
886 fn into_iter(self) -> Self::IntoIter {
887 self.classes.into_iter()
888 }
889}
890
891impl Display for EquivalenceGroup {
892 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
893 write!(f, "[")?;
894 let mut iter = self.iter();
895 if let Some(cls) = iter.next() {
896 write!(f, "{cls}")?;
897 }
898 for cls in iter {
899 write!(f, ", {cls}")?;
900 }
901 write!(f, "]")
902 }
903}
904
905impl From<Vec<EquivalenceClass>> for EquivalenceGroup {
906 fn from(classes: Vec<EquivalenceClass>) -> Self {
907 let mut result = Self {
908 map: classes
909 .iter()
910 .enumerate()
911 .flat_map(|(idx, cls)| {
912 cls.iter().map(move |expr| (Arc::clone(expr), idx))
913 })
914 .collect(),
915 classes,
916 };
917 result.remove_redundant_entries();
918 result
919 }
920}
921
922#[cfg(test)]
923mod tests {
924 use super::*;
925 use crate::equivalence::tests::create_test_params;
926 use crate::expressions::{BinaryExpr, Column, binary, col, lit};
927 use arrow::datatypes::{DataType, Field, Schema};
928
929 use datafusion_expr::Operator;
930
931 #[test]
932 fn test_bridge_groups() -> Result<()> {
933 let test_cases = vec![
935 (
937 vec![vec![1, 2, 3], vec![2, 4, 5], vec![11, 12, 9], vec![7, 6, 5]],
938 vec![vec![1, 2, 3, 4, 5, 6, 7], vec![9, 11, 12]],
940 ),
941 (
943 vec![vec![1, 2, 3], vec![3, 4, 5], vec![9, 8, 7], vec![7, 6, 5]],
944 vec![vec![1, 2, 3, 4, 5, 6, 7, 8, 9]],
946 ),
947 ];
948 for (entries, expected) in test_cases {
949 let entries = entries
950 .into_iter()
951 .map(|entry| {
952 entry.into_iter().map(|idx| {
953 let c = Column::new(format!("col_{idx}").as_str(), idx);
954 Arc::new(c) as _
955 })
956 })
957 .map(EquivalenceClass::new)
958 .collect::<Vec<_>>();
959 let expected = expected
960 .into_iter()
961 .map(|entry| {
962 entry.into_iter().map(|idx| {
963 let c = Column::new(format!("col_{idx}").as_str(), idx);
964 Arc::new(c) as _
965 })
966 })
967 .map(EquivalenceClass::new)
968 .collect::<Vec<_>>();
969 let eq_groups: EquivalenceGroup = entries.clone().into();
970 let eq_groups = eq_groups.classes;
971 let err_msg = format!(
972 "error in test entries: {entries:?}, expected: {expected:?}, actual:{eq_groups:?}"
973 );
974 assert_eq!(eq_groups.len(), expected.len(), "{err_msg}");
975 for idx in 0..eq_groups.len() {
976 assert_eq!(&eq_groups[idx], &expected[idx], "{err_msg}");
977 }
978 }
979 Ok(())
980 }
981
982 #[test]
983 fn test_remove_redundant_entries_eq_group() -> Result<()> {
984 let c = |idx| Arc::new(Column::new(format!("col_{idx}").as_str(), idx)) as _;
985 let entries = [
986 EquivalenceClass::new([c(1), c(1), lit(20)]),
987 EquivalenceClass::new([lit(30), lit(30)]),
988 EquivalenceClass::new([c(2), c(3), c(4)]),
989 ];
990 let expected = [
993 EquivalenceClass::new([c(1), lit(20)]),
994 EquivalenceClass::new([lit(30)]),
995 EquivalenceClass::new([c(2), c(3), c(4)]),
996 ];
997 let eq_groups = EquivalenceGroup::new(entries);
998 assert_eq!(eq_groups.classes, expected);
999 Ok(())
1000 }
1001
1002 #[test]
1003 fn test_schema_normalize_expr_with_equivalence() -> Result<()> {
1004 let col_a = Arc::new(Column::new("a", 0)) as Arc<dyn PhysicalExpr>;
1005 let col_b = Arc::new(Column::new("b", 1)) as _;
1006 let col_c = Arc::new(Column::new("c", 2)) as _;
1007 let (_, eq_properties) = create_test_params()?;
1009 let expressions = vec![
1012 (Arc::clone(&col_a), Arc::clone(&col_a)),
1016 (col_c, col_a),
1017 (Arc::clone(&col_b), Arc::clone(&col_b)),
1019 ];
1020 let eq_group = eq_properties.eq_group();
1021 for (expr, expected_eq) in expressions {
1022 assert!(expected_eq.eq(&eq_group.normalize_expr(expr)));
1023 }
1024
1025 Ok(())
1026 }
1027
1028 #[test]
1029 fn test_contains_any() {
1030 let lit_true = Arc::new(Literal::new(ScalarValue::from(true))) as _;
1031 let lit_false = Arc::new(Literal::new(ScalarValue::from(false))) as _;
1032 let col_a_expr = Arc::new(Column::new("a", 0)) as _;
1033 let col_b_expr = Arc::new(Column::new("b", 1)) as _;
1034 let col_c_expr = Arc::new(Column::new("c", 2)) as _;
1035
1036 let cls1 = EquivalenceClass::new([Arc::clone(&lit_true), col_a_expr]);
1037 let cls2 = EquivalenceClass::new([lit_true, col_b_expr]);
1038 let cls3 = EquivalenceClass::new([col_c_expr, lit_false]);
1039
1040 assert!(cls1.contains_any(&cls2));
1042 assert!(!cls1.contains_any(&cls3));
1044 assert!(!cls2.contains_any(&cls3));
1045 }
1046
1047 #[test]
1048 fn test_exprs_equal() -> Result<()> {
1049 struct TestCase {
1050 left: Arc<dyn PhysicalExpr>,
1051 right: Arc<dyn PhysicalExpr>,
1052 expected: bool,
1053 description: &'static str,
1054 }
1055
1056 let col_a = Arc::new(Column::new("a", 0)) as _;
1058 let col_b = Arc::new(Column::new("b", 1)) as _;
1059 let col_x = Arc::new(Column::new("x", 2)) as _;
1060 let col_y = Arc::new(Column::new("y", 3)) as _;
1061
1062 let lit_1 = Arc::new(Literal::new(ScalarValue::from(1))) as _;
1064 let lit_2 = Arc::new(Literal::new(ScalarValue::from(2))) as _;
1065
1066 let eq_group = EquivalenceGroup::new([
1068 EquivalenceClass::new([Arc::clone(&col_a), Arc::clone(&col_x)]),
1069 EquivalenceClass::new([Arc::clone(&col_b), Arc::clone(&col_y)]),
1070 ]);
1071
1072 let test_cases = vec![
1073 TestCase {
1075 left: Arc::clone(&col_a),
1076 right: Arc::clone(&col_a),
1077 expected: true,
1078 description: "Same column should be equal",
1079 },
1080 TestCase {
1082 left: Arc::clone(&col_a),
1083 right: Arc::clone(&col_x),
1084 expected: true,
1085 description: "Columns in same equivalence class should be equal",
1086 },
1087 TestCase {
1088 left: Arc::clone(&col_b),
1089 right: Arc::clone(&col_y),
1090 expected: true,
1091 description: "Columns in same equivalence class should be equal",
1092 },
1093 TestCase {
1094 left: Arc::clone(&col_a),
1095 right: Arc::clone(&col_b),
1096 expected: false,
1097 description: "Columns in different equivalence classes should not be equal",
1098 },
1099 TestCase {
1101 left: Arc::clone(&lit_1),
1102 right: Arc::clone(&lit_1),
1103 expected: true,
1104 description: "Same literal should be equal",
1105 },
1106 TestCase {
1107 left: Arc::clone(&lit_1),
1108 right: Arc::clone(&lit_2),
1109 expected: false,
1110 description: "Different literals should not be equal",
1111 },
1112 TestCase {
1114 left: Arc::new(BinaryExpr::new(
1115 Arc::clone(&col_a),
1116 Operator::Plus,
1117 Arc::clone(&col_b),
1118 )) as _,
1119 right: Arc::new(BinaryExpr::new(
1120 Arc::clone(&col_x),
1121 Operator::Plus,
1122 Arc::clone(&col_y),
1123 )) as _,
1124 expected: true,
1125 description: "Binary expressions with equivalent operands should be equal",
1126 },
1127 TestCase {
1128 left: Arc::new(BinaryExpr::new(
1129 Arc::clone(&col_a),
1130 Operator::Plus,
1131 Arc::clone(&col_b),
1132 )) as _,
1133 right: Arc::new(BinaryExpr::new(
1134 Arc::clone(&col_x),
1135 Operator::Plus,
1136 Arc::clone(&col_a),
1137 )) as _,
1138 expected: false,
1139 description: "Binary expressions with non-equivalent operands should not be equal",
1140 },
1141 TestCase {
1142 left: Arc::new(BinaryExpr::new(
1143 Arc::clone(&col_a),
1144 Operator::Plus,
1145 Arc::clone(&lit_1),
1146 )) as _,
1147 right: Arc::new(BinaryExpr::new(
1148 Arc::clone(&col_x),
1149 Operator::Plus,
1150 Arc::clone(&lit_1),
1151 )) as _,
1152 expected: true,
1153 description: "Binary expressions with equivalent column and same literal should be equal",
1154 },
1155 TestCase {
1156 left: Arc::new(BinaryExpr::new(
1157 Arc::new(BinaryExpr::new(
1158 Arc::clone(&col_a),
1159 Operator::Plus,
1160 Arc::clone(&col_b),
1161 )),
1162 Operator::Multiply,
1163 Arc::clone(&lit_1),
1164 )) as _,
1165 right: Arc::new(BinaryExpr::new(
1166 Arc::new(BinaryExpr::new(
1167 Arc::clone(&col_x),
1168 Operator::Plus,
1169 Arc::clone(&col_y),
1170 )),
1171 Operator::Multiply,
1172 Arc::clone(&lit_1),
1173 )) as _,
1174 expected: true,
1175 description: "Nested binary expressions with equivalent operands should be equal",
1176 },
1177 ];
1178
1179 for TestCase {
1180 left,
1181 right,
1182 expected,
1183 description,
1184 } in test_cases
1185 {
1186 let actual = eq_group.exprs_equal(&left, &right);
1187 assert_eq!(
1188 actual, expected,
1189 "{description}: Failed comparing {left:?} and {right:?}, expected {expected}, got {actual}"
1190 );
1191 }
1192
1193 Ok(())
1194 }
1195
1196 #[test]
1197 fn test_project_classes() -> Result<()> {
1198 let schema = Arc::new(Schema::new(vec![
1203 Field::new("a", DataType::Int32, false),
1204 Field::new("b", DataType::Int32, false),
1205 Field::new("c", DataType::Int32, false),
1206 ]));
1207 let mut group = EquivalenceGroup::default();
1208 group.add_equal_conditions(col("a", &schema)?, col("b", &schema)?);
1209
1210 let projected_schema = Arc::new(Schema::new(vec![
1211 Field::new("a+c", DataType::Int32, false),
1212 Field::new("b+c", DataType::Int32, false),
1213 ]));
1214
1215 let mapping = [
1216 (
1217 binary(
1218 col("a", &schema)?,
1219 Operator::Plus,
1220 col("c", &schema)?,
1221 &schema,
1222 )?,
1223 vec![(col("a+c", &projected_schema)?, 0)].into(),
1224 ),
1225 (
1226 binary(
1227 col("b", &schema)?,
1228 Operator::Plus,
1229 col("c", &schema)?,
1230 &schema,
1231 )?,
1232 vec![(col("b+c", &projected_schema)?, 1)].into(),
1233 ),
1234 ]
1235 .into_iter()
1236 .collect::<ProjectionMapping>();
1237
1238 let projected = group.project(&mapping);
1239
1240 assert!(!projected.is_empty());
1241 let first_normalized = projected.normalize_expr(col("a+c", &projected_schema)?);
1242 let second_normalized = projected.normalize_expr(col("b+c", &projected_schema)?);
1243
1244 assert!(first_normalized.eq(&second_normalized));
1245
1246 Ok(())
1247 }
1248}