Skip to main content

datafusion_physical_expr/equivalence/
class.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use 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/// Represents whether a constant expression's value is uniform or varies across
37/// partitions. Has two variants:
38/// - `Heterogeneous`: The constant expression may have different values for
39///   different partitions.
40/// - `Uniform(Option<ScalarValue>)`: The constant expression has the same value
41///   across all partitions, or is `None` if the value is unknown.
42#[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/// A structure representing a expression known to be constant in a physical
65/// execution plan.
66///
67/// The `ConstExpr` struct encapsulates an expression that is constant during
68/// the execution of a query. For example if a filter like `A = 5` appears
69/// earlier in the plan, `A` would become a constant in subsequent operations.
70///
71/// # Fields
72///
73/// - `expr`: Constant expression for a node in the physical plan.
74/// - `across_partitions`: A boolean flag indicating whether the constant
75///   expression is the same across partitions. If set to `true`, the constant
76///   expression has same value for all partitions. If set to `false`, the
77///   constant expression may have different values for different partitions.
78///
79/// # Example
80///
81/// ```rust
82/// # use datafusion_physical_expr::ConstExpr;
83/// # use datafusion_physical_expr::expressions::lit;
84/// let col = lit(5);
85/// // Create a constant expression from a physical expression:
86/// let const_expr = ConstExpr::from(col);
87/// ```
88#[derive(Clone, Debug)]
89pub struct ConstExpr {
90    /// The expression that is known to be constant (e.g. a `Column`).
91    pub expr: Arc<dyn PhysicalExpr>,
92    /// Indicates whether the constant have the same value across all partitions.
93    pub across_partitions: AcrossPartitions,
94}
95// TODO: The `ConstExpr` definition above can be in an inconsistent state where
96//       `expr` is a literal but `across_partitions` is not `Uniform`. Consider
97//       a refactor to ensure that `ConstExpr` is always in a consistent state
98//       (either by changing type definition, or by API constraints).
99
100impl ConstExpr {
101    /// Create a new constant expression from a physical expression, specifying
102    /// whether the constant expression is the same across partitions.
103    ///
104    /// Note that you can also use `ConstExpr::from` to create a constant
105    /// expression from just a physical expression, with the *safe* assumption
106    /// of heterogenous values across partitions unless the expression is a
107    /// literal.
108    pub fn new(expr: Arc<dyn PhysicalExpr>, across_partitions: AcrossPartitions) -> Self {
109        let mut result = ConstExpr::from(expr);
110        // Override the across partitions specification if the expression is not
111        // a literal.
112        if result.across_partitions == AcrossPartitions::Heterogeneous {
113            result.across_partitions = across_partitions;
114        }
115        result
116    }
117
118    /// Returns a [`Display`]able list of `ConstExpr`.
119    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        // By default, assume constant expressions are not same across partitions.
155        // However, if we have a literal, it will have a single value that is the
156        // same across all partitions.
157        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/// An `EquivalenceClass` is a set of [`Arc<dyn PhysicalExpr>`]s that are known
170/// to have the same value for all tuples in a relation. These are generated by
171/// equality predicates (e.g. `a = b`), typically equi-join conditions and
172/// equality conditions in filters.
173///
174/// Two `EquivalenceClass`es are equal if they contains the same expressions in
175/// without any ordering.
176#[derive(Clone, Debug, Default, Eq, PartialEq)]
177pub struct EquivalenceClass {
178    /// The expressions in this equivalence class. The order doesn't matter for
179    /// equivalence purposes.
180    pub(crate) exprs: IndexSet<Arc<dyn PhysicalExpr>>,
181    /// Indicates whether the expressions in this equivalence class have a
182    /// constant value. A `Some` value indicates constant-ness.
183    pub(crate) constant: Option<AcrossPartitions>,
184}
185
186impl EquivalenceClass {
187    // Create a new equivalence class from a pre-existing collection.
188    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    /// Return the "canonical" expression for this class (the first element)
197    /// if non-empty.
198    pub fn canonical_expr(&self) -> Option<&Arc<dyn PhysicalExpr>> {
199        self.exprs.iter().next()
200    }
201
202    /// Insert the expression into this class, meaning it is known to be equal to
203    /// all other expressions in this class.
204    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                // TODO: Return an error if constant values do not agree.
209                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    /// Inserts all the expressions from other into this class.
220    pub fn extend(&mut self, other: Self) {
221        self.exprs.extend(other.exprs);
222        match (&self.constant, &other.constant) {
223            (Some(across), Some(_)) => {
224                // TODO: Return an error if constant values do not agree.
225                if across == &AcrossPartitions::Heterogeneous {
226                    self.constant = other.constant;
227                }
228            }
229            (None, Some(_)) => self.constant = other.constant,
230            (_, None) => {}
231        }
232    }
233
234    /// Returns whether this equivalence class has any entries in common with
235    /// `other`.
236    pub fn contains_any(&self, other: &Self) -> bool {
237        self.exprs.intersection(&other.exprs).next().is_some()
238    }
239
240    /// Returns whether this equivalence class is trivial, meaning that it is
241    /// either empty, or contains a single expression that is not a constant.
242    /// Such classes are not useful, and can be removed from equivalence groups.
243    pub fn is_trivial(&self) -> bool {
244        self.exprs.is_empty() || (self.exprs.len() == 1 && self.constant.is_none())
245    }
246
247    /// Adds the given offset to all columns in the expressions inside this
248    /// class. This is used when schemas are appended, e.g. in joins.
249    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/// A collection of distinct `EquivalenceClass`es. This object supports fast
303/// lookups of expressions and their equivalence classes.
304#[derive(Clone, Debug, Default)]
305pub struct EquivalenceGroup {
306    /// A mapping from expressions to their equivalence class key.
307    map: IndexMap<Arc<dyn PhysicalExpr>, usize>,
308    /// The equivalence classes in this group.
309    classes: Vec<EquivalenceClass>,
310}
311
312impl EquivalenceGroup {
313    /// Creates an equivalence group from the given equivalence classes.
314    pub fn new(classes: impl IntoIterator<Item = EquivalenceClass>) -> Self {
315        classes.into_iter().collect::<Vec<_>>().into()
316    }
317
318    /// Adds `expr` as a constant expression to this equivalence group.
319    pub fn add_constant(&mut self, const_expr: ConstExpr) {
320        // If the expression is already in an equivalence class, we should
321        // adjust the constant-ness of the class if necessary:
322        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                // TODO: Return an error if constant values do not agree.
326                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 the expression is not in any equivalence class, but has the same
335        // constant value with some class, add it to that class:
336        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        // Otherwise, create a new class with the expression as the only member:
350        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    /// Removes constant expressions that may change across partitions.
359    /// This method should be used when merging data from different partitions.
360    /// Returns whether any change was made to the equivalence group.
361    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                    // If this class becomes trivial, remove it entirely:
369                    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    /// Adds the equality `left` = `right` to this equivalence group. New
381    /// equality conditions often arise after steps like `Filter(a = b)`,
382    /// `Alias(a, a as b)` etc. Returns whether the given equality defines
383    /// a new equivalence class.
384    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                // If the given left and right sides belong to different classes,
394                // we should unify/bridge these classes.
395                match first_idx.cmp(&second_idx) {
396                    // The equality is already known, return and signal this:
397                    std::cmp::Ordering::Equal => return false,
398                    // Swap indices to ensure `first_idx` is the lesser index.
399                    std::cmp::Ordering::Greater => {
400                        std::mem::swap(&mut first_idx, &mut second_idx);
401                    }
402                    _ => {}
403                }
404                // Remove the class at `second_idx` and merge its values with
405                // the class at `first_idx`. The convention above makes sure
406                // that `first_idx` is still valid after removing `second_idx`.
407                let other_class = self.remove_class_at_idx(second_idx);
408                // Update the lookup table for the second class:
409                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                // Right side is new, extend left side's class:
414                self.map.insert(Arc::clone(&right), group_idx);
415                self.classes[group_idx].push(right);
416            }
417            (None, Some(group_idx)) => {
418                // Left side is new, extend right side's class:
419                self.map.insert(Arc::clone(&left), group_idx);
420                self.classes[group_idx].push(left);
421            }
422            (None, None) => {
423                // None of the expressions is among existing classes.
424                // Create a new equivalence class and extend the group.
425                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    /// Removes the equivalence class at the given index from this group.
435    fn remove_class_at_idx(&mut self, idx: usize) -> EquivalenceClass {
436        // Remove the class at the given index:
437        let cls = self.classes.swap_remove(idx);
438        // Remove its entries from the lookup table:
439        for expr in cls.iter() {
440            self.map.swap_remove(expr);
441        }
442        // Update the lookup table for the moved class:
443        if idx < self.classes.len() {
444            Self::update_lookup_table(&mut self.map, &self.classes[idx], idx);
445        }
446        cls
447    }
448
449    /// Updates the entry in lookup table for the given equivalence class with
450    /// the given index.
451    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    /// Removes redundant entries from this group. Returns whether any change
462    /// was made to the equivalence group.
463    fn remove_redundant_entries(&mut self) -> bool {
464        // First, remove trivial equivalence classes:
465        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        // Then, unify/bridge groups that have common expressions:
473        self.bridge_classes() || change
474    }
475
476    /// This utility function unifies/bridges classes that have common expressions.
477    /// For example, assume that we have [`EquivalenceClass`]es `[a, b]` and `[b, c]`.
478    /// Since both classes contain `b`, columns `a`, `b` and `c` are actually all
479    /// equal and belong to one class. This utility converts merges such classes.
480    /// Returns whether any change was made to the equivalence group.
481    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    /// Extends this equivalence group with the `other` equivalence group.
499    /// Returns whether any equivalence classes were unified/bridged as a
500    /// result of the extension process.
501    pub fn extend(&mut self, other: Self) -> bool {
502        for (idx, cls) in other.classes.iter().enumerate() {
503            // Update the lookup table for the new class:
504            Self::update_lookup_table(&mut self.map, cls, idx);
505        }
506        self.classes.extend(other.classes);
507        self.bridge_classes()
508    }
509
510    /// Normalizes the given physical expression according to this group. The
511    /// expression is replaced with the first (canonical) expression in the
512    /// equivalence class it matches with (if any).
513    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        // The unwrap above is safe because the closure always returns `Ok`.
524    }
525
526    /// Normalizes the given sort expression according to this group. The
527    /// underlying physical expression is replaced with the first expression in
528    /// the equivalence class it matches with (if any). If the underlying
529    /// expression does not belong to any equivalence class in this group,
530    /// returns the sort expression as is.
531    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    /// Normalizes the given sort expressions (i.e. `sort_exprs`) by:
540    /// - Replacing sections that belong to some equivalence class in the
541    ///   with the first entry in the matching equivalence class.
542    /// - Removing expressions that have a constant value.
543    ///
544    /// If columns `a` and `b` are known to be equal, `d` is known to be a
545    /// constant, and `sort_exprs` is `[b ASC, d DESC, c ASC, a ASC]`, this
546    /// function would return `[a ASC, c ASC, a ASC]`.
547    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    /// Returns `true` when `expr` is a *globally* constant column, safe to drop
558    /// from a required ordering. Only [`AcrossPartitions::Uniform`] qualifies; a
559    /// [`AcrossPartitions::Heterogeneous`] value is constant within a partition
560    /// but varies across partitions, so it still discriminates the order once
561    /// partitions are merged and must be kept.
562    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    /// Normalizes the given sort requirement according to this group. The
570    /// underlying physical expression is replaced with the first expression in
571    /// the equivalence class it matches with (if any). If the underlying
572    /// expression does not belong to any equivalence class in this group,
573    /// returns the given sort requirement as is.
574    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    /// Normalizes the given sort requirements (i.e. `sort_reqs`) by:
583    /// - Replacing sections that belong to some equivalence class in the
584    ///   with the first entry in the matching equivalence class.
585    /// - Removing expressions that have a constant value.
586    ///
587    /// If columns `a` and `b` are known to be equal, `d` is known to be a
588    /// constant, and `sort_reqs` is `[b ASC, d DESC, c ASC, a ASC]`, this
589    /// function would return `[a ASC, c ASC, a ASC]`.
590    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    /// Perform an indirect projection of `expr` by consulting the equivalence
601    /// classes.
602    fn project_expr_indirect(
603        aug_mapping: &AugmentedMapping,
604        expr: &Arc<dyn PhysicalExpr>,
605    ) -> Option<Arc<dyn PhysicalExpr>> {
606        // Literals don't need to be projected
607        if expr.downcast_ref::<Literal>().is_some() {
608            return Some(Arc::clone(expr));
609        }
610
611        // The given expression is not inside the mapping, so we try to project
612        // indirectly using equivalence classes.
613        for (targets, eq_class) in aug_mapping.values() {
614            // If we match an equivalent expression to a source expression in
615            // the mapping, then we can project. For example, if we have the
616            // mapping `(a as a1, a + c)` and the equivalence `a == b`,
617            // expression `b` projects to `a1`.
618            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        // Project a non-leaf expression by projecting its children.
624        let children = expr.children();
625        if children.is_empty() {
626            // A leaf expression should be inside the mapping.
627            return None;
628        }
629        children
630            .into_iter()
631            .map(|child| {
632                // First, we try to project children with an exact match. If
633                // we are unable to do this, we consult equivalence classes.
634                if let Some((targets, _)) = aug_mapping.get(child) {
635                    // If we match the source, we can project directly:
636                    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    /// Projects `expr` according to the given projection mapping.
660    /// If the resulting expression is invalid after projection, returns `None`.
661    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            // If we match the source, we can project directly:
668            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    /// Projects `expressions` according to the given projection mapping.
677    /// This function is similar to [`Self::project_expr`], but projects multiple
678    /// expressions at once more efficiently than calling `project_expr` for each
679    /// expression.
680    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                // If we match the source, we can project directly:
689                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    /// Projects this equivalence group according to the given projection mapping.
700    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        // The key is the source expression, and the value is the equivalence
707        // class that contains the corresponding target expression.
708        let mut new_constants = vec![];
709        let mut new_classes = IndexMap::<_, EquivalenceClass>::new();
710        for (source, targets) in mapping.iter() {
711            // We need to find equivalent projected expressions. For example,
712            // consider a table with columns `[a, b, c]` with `a` == `b`, and
713            // projection `[a + c, b + c]`. To conclude that `a + c == b + c`,
714            // we first normalize all source expressions in the mapping, then
715            // merge all equivalent expressions into the classes.
716            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            // Save new constants arising from the projection:
722            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        // Union projected classes with new classes to make up the result:
731        let classes = projected_classes
732            .chain(new_classes.into_values())
733            .filter(|cls| !cls.is_trivial());
734        let mut result = Self::new(classes);
735        // Add new constants arising from the projection to the equivalence group:
736        for constant in new_constants {
737            result.add_constant(constant);
738        }
739        result
740    }
741
742    /// Returns a `Some` value if the expression is constant according to
743    /// equivalence group, and `None` otherwise. The `Some` variant contains
744    /// an `AcrossPartitions` value indicating whether the expression is
745    /// constant across partitions, and its actual value (if available).
746    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        // TODO: This function should be able to return values of non-literal
759        //       complex constants as well; e.g. it should return `8` for the
760        //       expression `3 + 5`, not an unknown `heterogenous` value.
761        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    /// Returns the equivalence class containing `expr`. If no equivalence class
772    /// contains `expr`, returns `None`.
773    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    /// Combine equivalence groups of the given join children.
781    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                // In we have an inner join, expressions in the "on" condition
799                // are equal in the resulting table.
800                if join_type == &JoinType::Inner {
801                    for (lhs, rhs) in on.iter() {
802                        let new_lhs = Arc::clone(lhs);
803                        // Rewrite rhs to point to the right side of the join:
804                        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    /// Checks if two expressions are equal directly or through equivalence
820    /// classes. For complex expressions (e.g. `a + b`), checks that the
821    /// expression trees are structurally identical and their leaf nodes are
822    /// equivalent either directly or through equivalence classes.
823    pub fn exprs_equal(
824        &self,
825        left: &Arc<dyn PhysicalExpr>,
826        right: &Arc<dyn PhysicalExpr>,
827    ) -> bool {
828        // Direct equality check
829        if left.eq(right) {
830            return true;
831        }
832
833        // Check if expressions are equivalent through equivalence classes
834        // We need to check both directions since expressions might be in different classes
835        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        // For non-leaf nodes, check structural equality
847        let left_children = left.children();
848        let right_children = right.children();
849
850        // If either expression is a leaf node and we haven't found equality yet,
851        // they must be different
852        if left_children.is_empty() || right_children.is_empty() {
853            return false;
854        }
855
856        // Type equality check through reflection
857        if (left as &dyn Any).type_id() != (right as &dyn Any).type_id() {
858            return false;
859        }
860
861        // Check if the number of children is the same
862        if left_children.len() != right_children.len() {
863            return false;
864        }
865
866        // Check if all children are equal
867        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        // First entry in the tuple is argument, second entry is the bridged result
934        let test_cases = vec![
935            // ------- TEST CASE 1 -----------//
936            (
937                vec![vec![1, 2, 3], vec![2, 4, 5], vec![11, 12, 9], vec![7, 6, 5]],
938                // Expected is compared with set equality. Order of the specific results may change.
939                vec![vec![1, 2, 3, 4, 5, 6, 7], vec![9, 11, 12]],
940            ),
941            // ------- TEST CASE 2 -----------//
942            (
943                vec![vec![1, 2, 3], vec![3, 4, 5], vec![9, 8, 7], vec![7, 6, 5]],
944                // Expected
945                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        // Given equivalences classes are not in succinct form.
991        // Expected form is the most plain representation that is functionally same.
992        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        // Assume that column a and c are aliases.
1008        let (_, eq_properties) = create_test_params()?;
1009        // Test cases for equivalence normalization. First entry in the tuple is
1010        // the argument, second entry is expected result after normalization.
1011        let expressions = vec![
1012            // Normalized version of the column a and c should go to a
1013            // (by convention all the expressions inside equivalence class are mapped to the first entry
1014            // in this case a is the first entry in the equivalence class.)
1015            (Arc::clone(&col_a), Arc::clone(&col_a)),
1016            (col_c, col_a),
1017            // Cannot normalize column b
1018            (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        // lit_true is common
1041        assert!(cls1.contains_any(&cls2));
1042        // there is no common entry
1043        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        // Create test columns
1057        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        // Create test literals
1063        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        // Create equivalence group with classes (a = x) and (b = y)
1067        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            // Basic equality tests
1074            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            // Equivalence class tests
1081            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            // Literal tests
1100            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            // Complex expression tests
1113            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        // - columns: [a, b, c].
1199        // - "a" and "b" in the same equivalence class.
1200        // - then after a+c, b+c projection col(0) and col(1) must be
1201        // in the same class too.
1202        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}