Skip to main content

datafusion_optimizer/
common_subexpr_eliminate.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
18//! [`CommonSubexprEliminate`] to avoid redundant computation of common sub-expressions
19
20use std::collections::BTreeSet;
21use std::fmt::Debug;
22use std::sync::Arc;
23
24use crate::{OptimizerConfig, OptimizerRule};
25
26use crate::optimizer::ApplyOrder;
27use crate::utils::NamePreserver;
28use datafusion_common::alias::AliasGenerator;
29
30use datafusion_common::cse::{CSE, CSEController, FoundCommonNodes};
31use datafusion_common::tree_node::{Transformed, TreeNode};
32use datafusion_common::{Column, DFSchema, DFSchemaRef, Result, qualified_name};
33use datafusion_expr::expr::{Alias, HigherOrderFunction, ScalarFunction};
34use datafusion_expr::logical_plan::{
35    Aggregate, Filter, LogicalPlan, Projection, Sort, Window,
36};
37use datafusion_expr::{
38    BinaryExpr, Case, Expr, ExpressionPlacement, Operator, SortExpr, col,
39};
40
41const CSE_PREFIX: &str = "__common_expr";
42
43/// Performs Common Sub-expression Elimination optimization.
44///
45/// This optimization improves query performance by computing expressions that
46/// appear more than once and reusing those results rather than re-computing the
47/// same value
48///
49/// Currently only common sub-expressions within a single `LogicalPlan` are
50/// eliminated.
51///
52/// # Example
53///
54/// Given a projection that computes the same expensive expression
55/// multiple times such as parsing as string as a date with `to_date` twice:
56///
57/// ```text
58/// ProjectionExec(expr=[extract (day from to_date(c1)), extract (year from to_date(c1))])
59/// ```
60///
61/// This optimization will rewrite the plan to compute the common expression once
62/// using a new `ProjectionExec` and then rewrite the original expressions to
63/// refer to that new column.
64///
65/// ```text
66/// ProjectionExec(exprs=[extract (day from new_col), extract (year from new_col)]) <-- reuse here
67///   ProjectionExec(exprs=[to_date(c1) as new_col]) <-- compute to_date once
68/// ```
69#[derive(Debug)]
70pub struct CommonSubexprEliminate {}
71
72impl CommonSubexprEliminate {
73    pub fn new() -> Self {
74        Self {}
75    }
76
77    fn try_optimize_proj(
78        &self,
79        projection: Projection,
80        config: &dyn OptimizerConfig,
81    ) -> Result<Transformed<LogicalPlan>> {
82        let Projection {
83            expr,
84            input,
85            schema,
86            ..
87        } = projection;
88        let input = Arc::unwrap_or_clone(input);
89        self.try_unary_plan(expr, input, config)?
90            .map_data(|(new_expr, new_input)| {
91                Projection::try_new_with_schema(new_expr, Arc::new(new_input), schema)
92                    .map(LogicalPlan::Projection)
93            })
94    }
95
96    fn try_optimize_sort(
97        &self,
98        sort: Sort,
99        config: &dyn OptimizerConfig,
100    ) -> Result<Transformed<LogicalPlan>> {
101        let Sort { expr, input, fetch } = sort;
102        let input = Arc::unwrap_or_clone(input);
103        let (sort_expressions, sort_params): (Vec<_>, Vec<(_, _)>) = expr
104            .into_iter()
105            .map(|sort| (sort.expr, (sort.asc, sort.nulls_first)))
106            .unzip();
107        let new_sort = self
108            .try_unary_plan(sort_expressions, input, config)?
109            .update_data(|(new_expr, new_input)| {
110                LogicalPlan::Sort(Sort {
111                    expr: new_expr
112                        .into_iter()
113                        .zip(sort_params)
114                        .map(|(expr, (asc, nulls_first))| SortExpr {
115                            expr,
116                            asc,
117                            nulls_first,
118                        })
119                        .collect(),
120                    input: Arc::new(new_input),
121                    fetch,
122                })
123            });
124        Ok(new_sort)
125    }
126
127    fn try_optimize_filter(
128        &self,
129        filter: Filter,
130        config: &dyn OptimizerConfig,
131    ) -> Result<Transformed<LogicalPlan>> {
132        let Filter {
133            predicate, input, ..
134        } = filter;
135        let input = Arc::unwrap_or_clone(input);
136        let expr = vec![predicate];
137        self.try_unary_plan(expr, input, config)?
138            .map_data(|(mut new_expr, new_input)| {
139                assert_eq!(new_expr.len(), 1); // passed in vec![predicate]
140                let new_predicate = new_expr.pop().unwrap();
141                Filter::try_new(new_predicate, Arc::new(new_input))
142                    .map(LogicalPlan::Filter)
143            })
144    }
145
146    fn try_optimize_window(
147        &self,
148        window: Window,
149        config: &dyn OptimizerConfig,
150    ) -> Result<Transformed<LogicalPlan>> {
151        // Collects window expressions from consecutive `LogicalPlan::Window` nodes into
152        // a list.
153        let (window_expr_list, window_schemas, input) =
154            get_consecutive_window_exprs(window);
155
156        // Extract common sub-expressions from the list.
157
158        match CSE::new(ExprCSEController::new(
159            config.alias_generator().as_ref(),
160            ExprMask::Normal,
161        ))
162        .extract_common_nodes(window_expr_list)?
163        {
164            // If there are common sub-expressions, then the insert a projection node
165            // with the common expressions between the new window nodes and the
166            // original input.
167            FoundCommonNodes::Yes {
168                common_nodes: common_exprs,
169                new_nodes_list: new_exprs_list,
170                original_nodes_list: original_exprs_list,
171            } => build_common_expr_project_plan(input, common_exprs).map(|new_input| {
172                Transformed::yes((new_exprs_list, new_input, Some(original_exprs_list)))
173            }),
174            FoundCommonNodes::No {
175                original_nodes_list: original_exprs_list,
176            } => Ok(Transformed::no((original_exprs_list, input, None))),
177        }?
178        // Recurse into the new input.
179        // (This is similar to what a `ApplyOrder::TopDown` optimizer rule would do.)
180        .transform_data(|(new_window_expr_list, new_input, window_expr_list)| {
181            self.rewrite(new_input, config)?.map_data(|new_input| {
182                Ok((new_window_expr_list, new_input, window_expr_list))
183            })
184        })?
185        // Rebuild the consecutive window nodes.
186        .map_data(|(new_window_expr_list, new_input, window_expr_list)| {
187            // If there were common expressions extracted, then we need to make sure
188            // we restore the original column names.
189            // TODO: Although `find_common_exprs()` inserts aliases around extracted
190            //  common expressions this doesn't mean that the original column names
191            //  (schema) are preserved due to the inserted aliases are not always at
192            //  the top of the expression.
193            //  Let's consider improving `find_common_exprs()` to always keep column
194            //  names and get rid of additional name preserving logic here.
195            if let Some(window_expr_list) = window_expr_list {
196                let name_preserver = NamePreserver::new_for_projection();
197                let saved_names = window_expr_list
198                    .iter()
199                    .map(|exprs| {
200                        exprs
201                            .iter()
202                            .map(|expr| name_preserver.save(expr))
203                            .collect::<Vec<_>>()
204                    })
205                    .collect::<Vec<_>>();
206                new_window_expr_list.into_iter().zip(saved_names).try_rfold(
207                    new_input,
208                    |plan, (new_window_expr, saved_names)| {
209                        let new_window_expr = new_window_expr
210                            .into_iter()
211                            .zip(saved_names)
212                            .map(|(new_window_expr, saved_name)| {
213                                saved_name.restore(new_window_expr)
214                            })
215                            .collect::<Vec<_>>();
216                        Window::try_new(new_window_expr, Arc::new(plan))
217                            .map(LogicalPlan::Window)
218                    },
219                )
220            } else {
221                new_window_expr_list
222                    .into_iter()
223                    .zip(window_schemas)
224                    .try_rfold(new_input, |plan, (new_window_expr, schema)| {
225                        Window::try_new_with_schema(
226                            new_window_expr,
227                            Arc::new(plan),
228                            schema,
229                        )
230                        .map(LogicalPlan::Window)
231                    })
232            }
233        })
234    }
235
236    fn try_optimize_aggregate(
237        &self,
238        aggregate: Aggregate,
239        config: &dyn OptimizerConfig,
240    ) -> Result<Transformed<LogicalPlan>> {
241        let Aggregate {
242            group_expr,
243            aggr_expr,
244            input,
245            schema,
246            ..
247        } = aggregate;
248        let input = Arc::unwrap_or_clone(input);
249        // Extract common sub-expressions from the aggregate and grouping expressions.
250        match CSE::new(ExprCSEController::new(
251            config.alias_generator().as_ref(),
252            ExprMask::Normal,
253        ))
254        .extract_common_nodes(vec![group_expr, aggr_expr])?
255        {
256            // If there are common sub-expressions, then insert a projection node
257            // with the common expressions between the new aggregate node and the
258            // original input.
259            FoundCommonNodes::Yes {
260                common_nodes: common_exprs,
261                new_nodes_list: mut new_exprs_list,
262                original_nodes_list: mut original_exprs_list,
263            } => {
264                let new_aggr_expr = new_exprs_list.pop().unwrap();
265                let new_group_expr = new_exprs_list.pop().unwrap();
266
267                build_common_expr_project_plan(input, common_exprs).map(|new_input| {
268                    let aggr_expr = original_exprs_list.pop().unwrap();
269                    Transformed::yes((
270                        new_aggr_expr,
271                        new_group_expr,
272                        new_input,
273                        Some(aggr_expr),
274                    ))
275                })
276            }
277
278            FoundCommonNodes::No {
279                original_nodes_list: mut original_exprs_list,
280            } => {
281                let new_aggr_expr = original_exprs_list.pop().unwrap();
282                let new_group_expr = original_exprs_list.pop().unwrap();
283
284                Ok(Transformed::no((
285                    new_aggr_expr,
286                    new_group_expr,
287                    input,
288                    None,
289                )))
290            }
291        }?
292        // Recurse into the new input.
293        // (This is similar to what a `ApplyOrder::TopDown` optimizer rule would do.)
294        .transform_data(|(new_aggr_expr, new_group_expr, new_input, aggr_expr)| {
295            self.rewrite(new_input, config)?.map_data(|new_input| {
296                Ok((
297                    new_aggr_expr,
298                    new_group_expr,
299                    aggr_expr,
300                    Arc::new(new_input),
301                ))
302            })
303        })?
304        // Try extracting common aggregate expressions and rebuild the aggregate node.
305        .transform_data(
306            |(new_aggr_expr, new_group_expr, aggr_expr, new_input)| {
307                // Extract common aggregate sub-expressions from the aggregate expressions.
308                match CSE::new(ExprCSEController::new(
309                    config.alias_generator().as_ref(),
310                    ExprMask::NormalAndAggregates,
311                ))
312                .extract_common_nodes(vec![new_aggr_expr])?
313                {
314                    FoundCommonNodes::Yes {
315                        common_nodes: common_exprs,
316                        new_nodes_list: mut new_exprs_list,
317                        original_nodes_list: mut original_exprs_list,
318                    } => {
319                        let rewritten_aggr_expr = new_exprs_list.pop().unwrap();
320                        let new_aggr_expr = original_exprs_list.pop().unwrap();
321                        let saved_names = if let Some(aggr_expr) = aggr_expr {
322                            let name_preserver = NamePreserver::new_for_projection();
323                            aggr_expr
324                                .iter()
325                                .map(|expr| Some(name_preserver.save(expr)))
326                                .collect::<Vec<_>>()
327                        } else {
328                            (0..new_aggr_expr.len()).map(|_| None).collect()
329                        };
330
331                        let mut agg_exprs = common_exprs
332                            .into_iter()
333                            .map(|(expr, expr_alias)| expr.alias(expr_alias))
334                            .collect::<Vec<_>>();
335
336                        let mut proj_exprs = vec![];
337                        for expr in &new_group_expr {
338                            extract_expressions(expr, &mut proj_exprs)
339                        }
340                        for ((expr_rewritten, expr_orig), saved_name) in
341                            rewritten_aggr_expr
342                                .into_iter()
343                                .zip(new_aggr_expr)
344                                .zip(saved_names)
345                        {
346                            if expr_rewritten == expr_orig {
347                                let expr_rewritten = if let Some(saved_name) = saved_name
348                                {
349                                    saved_name.restore(expr_rewritten)
350                                } else {
351                                    expr_rewritten
352                                };
353                                if let Expr::Alias(Alias { expr, name, .. }) =
354                                    expr_rewritten
355                                {
356                                    agg_exprs.push(expr.alias(&name));
357                                    proj_exprs
358                                        .push(Expr::Column(Column::from_name(name)));
359                                } else {
360                                    let expr_alias =
361                                        config.alias_generator().next(CSE_PREFIX);
362                                    let (qualifier, field_name) =
363                                        expr_rewritten.qualified_name();
364                                    let out_name =
365                                        qualified_name(qualifier.as_ref(), &field_name);
366
367                                    agg_exprs.push(expr_rewritten.alias(&expr_alias));
368                                    proj_exprs.push(
369                                        Expr::Column(Column::from_name(expr_alias))
370                                            .alias(out_name),
371                                    );
372                                }
373                            } else {
374                                proj_exprs.push(expr_rewritten);
375                            }
376                        }
377
378                        let agg = LogicalPlan::Aggregate(Aggregate::try_new(
379                            new_input,
380                            new_group_expr,
381                            agg_exprs,
382                        )?);
383                        Projection::try_new(proj_exprs, Arc::new(agg))
384                            .map(|p| Transformed::yes(LogicalPlan::Projection(p)))
385                    }
386
387                    // If there aren't any common aggregate sub-expressions, then just
388                    // rebuild the aggregate node.
389                    FoundCommonNodes::No {
390                        original_nodes_list: mut original_exprs_list,
391                    } => {
392                        let rewritten_aggr_expr = original_exprs_list.pop().unwrap();
393
394                        // If there were common expressions extracted, then we need to
395                        // make sure we restore the original column names.
396                        // TODO: Although `find_common_exprs()` inserts aliases around
397                        //  extracted common expressions this doesn't mean that the
398                        //  original column names (schema) are preserved due to the
399                        //  inserted aliases are not always at the top of the
400                        //  expression.
401                        //  Let's consider improving `find_common_exprs()` to always
402                        //  keep column names and get rid of additional name
403                        //  preserving logic here.
404                        if let Some(aggr_expr) = aggr_expr {
405                            let name_preserver = NamePreserver::new_for_projection();
406                            let saved_names = aggr_expr
407                                .iter()
408                                .map(|expr| name_preserver.save(expr))
409                                .collect::<Vec<_>>();
410                            let new_aggr_expr = rewritten_aggr_expr
411                                .into_iter()
412                                .zip(saved_names)
413                                .map(|(new_expr, saved_name)| {
414                                    saved_name.restore(new_expr)
415                                })
416                                .collect::<Vec<Expr>>();
417
418                            // Since `group_expr` may have changed, schema may also.
419                            // Use `try_new()` method.
420                            Aggregate::try_new(new_input, new_group_expr, new_aggr_expr)
421                                .map(LogicalPlan::Aggregate)
422                                .map(Transformed::no)
423                        } else {
424                            Aggregate::try_new_with_schema(
425                                new_input,
426                                new_group_expr,
427                                rewritten_aggr_expr,
428                                schema,
429                            )
430                            .map(LogicalPlan::Aggregate)
431                            .map(Transformed::no)
432                        }
433                    }
434                }
435            },
436        )
437    }
438
439    /// Rewrites the expr list and input to remove common subexpressions
440    ///
441    /// # Parameters
442    ///
443    /// * `exprs`: List of expressions in the node
444    /// * `input`: input plan (that produces the columns referred to in `exprs`)
445    ///
446    /// # Return value
447    ///
448    ///  Returns `(rewritten_exprs, new_input)`. `new_input` is either:
449    ///
450    /// 1. The original `input` of no common subexpressions were extracted
451    /// 2. A newly added projection on top of the original input
452    ///    that computes the common subexpressions
453    fn try_unary_plan(
454        &self,
455        exprs: Vec<Expr>,
456        input: LogicalPlan,
457        config: &dyn OptimizerConfig,
458    ) -> Result<Transformed<(Vec<Expr>, LogicalPlan)>> {
459        // Extract common sub-expressions from the expressions.
460        match CSE::new(ExprCSEController::new(
461            config.alias_generator().as_ref(),
462            ExprMask::Normal,
463        ))
464        .extract_common_nodes(vec![exprs])?
465        {
466            FoundCommonNodes::Yes {
467                common_nodes: common_exprs,
468                new_nodes_list: mut new_exprs_list,
469                original_nodes_list: _,
470            } => {
471                let new_exprs = new_exprs_list.pop().unwrap();
472                build_common_expr_project_plan(input, common_exprs)
473                    .map(|new_input| Transformed::yes((new_exprs, new_input)))
474            }
475            FoundCommonNodes::No {
476                original_nodes_list: mut original_exprs_list,
477            } => {
478                let new_exprs = original_exprs_list.pop().unwrap();
479                Ok(Transformed::no((new_exprs, input)))
480            }
481        }?
482        // Recurse into the new input.
483        // (This is similar to what a `ApplyOrder::TopDown` optimizer rule would do.)
484        .transform_data(|(new_exprs, new_input)| {
485            self.rewrite(new_input, config)?
486                .map_data(|new_input| Ok((new_exprs, new_input)))
487        })
488    }
489}
490
491/// Get all window expressions inside the consecutive window operators.
492///
493/// Returns the window expressions, and the input to the deepest child
494/// LogicalPlan.
495///
496/// For example, if the input window looks like
497///
498/// ```text
499///   LogicalPlan::Window(exprs=[a, b, c])
500///     LogicalPlan::Window(exprs=[d])
501///       InputPlan
502/// ```
503///
504/// Returns:
505/// *  `window_exprs`: `[[a, b, c], [d]]`
506/// * InputPlan
507///
508/// Consecutive window expressions may refer to same complex expression.
509///
510/// If same complex expression is referred more than once by subsequent
511/// `WindowAggr`s, we can cache complex expression by evaluating it with a
512/// projection before the first WindowAggr.
513///
514/// This enables us to cache complex expression "c3+c4" for following plan:
515///
516/// ```text
517/// WindowAggr: windowExpr=[[sum(c9) ORDER BY [c3 + c4] RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW]]
518/// --WindowAggr: windowExpr=[[sum(c9) ORDER BY [c3 + c4] RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW]]
519/// ```
520///
521/// where, it is referred once by each `WindowAggr` (total of 2) in the plan.
522fn get_consecutive_window_exprs(
523    window: Window,
524) -> (Vec<Vec<Expr>>, Vec<DFSchemaRef>, LogicalPlan) {
525    let mut window_expr_list = vec![];
526    let mut window_schemas = vec![];
527    let mut plan = LogicalPlan::Window(window);
528    while let LogicalPlan::Window(Window {
529        input,
530        window_expr,
531        schema,
532    }) = plan
533    {
534        window_expr_list.push(window_expr);
535        window_schemas.push(schema);
536
537        plan = Arc::unwrap_or_clone(input);
538    }
539    (window_expr_list, window_schemas, plan)
540}
541
542impl OptimizerRule for CommonSubexprEliminate {
543    fn supports_rewrite(&self) -> bool {
544        true
545    }
546
547    fn apply_order(&self) -> Option<ApplyOrder> {
548        // This rule handles recursion itself in a `ApplyOrder::TopDown` like manner.
549        // This is because in some cases adjacent nodes are collected (e.g. `Window`) and
550        // CSEd as a group, which can't be done in a simple `ApplyOrder::TopDown` rule.
551        None
552    }
553
554    #[cfg_attr(feature = "recursive_protection", recursive::recursive)]
555    fn rewrite(
556        &self,
557        plan: LogicalPlan,
558        config: &dyn OptimizerConfig,
559    ) -> Result<Transformed<LogicalPlan>> {
560        let original_schema = Arc::clone(plan.schema());
561
562        let optimized_plan = match plan {
563            LogicalPlan::Projection(proj) => self.try_optimize_proj(proj, config)?,
564            LogicalPlan::Sort(sort) => self.try_optimize_sort(sort, config)?,
565            LogicalPlan::Filter(filter) => self.try_optimize_filter(filter, config)?,
566            LogicalPlan::Window(window) => self.try_optimize_window(window, config)?,
567            LogicalPlan::Aggregate(agg) => self.try_optimize_aggregate(agg, config)?,
568            LogicalPlan::Join(_)
569            | LogicalPlan::Repartition(_)
570            | LogicalPlan::Union(_)
571            | LogicalPlan::TableScan(_)
572            | LogicalPlan::Values(_)
573            | LogicalPlan::EmptyRelation(_)
574            | LogicalPlan::Subquery(_)
575            | LogicalPlan::SubqueryAlias(_)
576            | LogicalPlan::Limit(_)
577            | LogicalPlan::Ddl(_)
578            | LogicalPlan::Explain(_)
579            | LogicalPlan::Analyze(_)
580            | LogicalPlan::Statement(_)
581            | LogicalPlan::DescribeTable(_)
582            | LogicalPlan::Distinct(_)
583            | LogicalPlan::Extension(_)
584            | LogicalPlan::Dml(_)
585            | LogicalPlan::Copy(_)
586            | LogicalPlan::Unnest(_)
587            | LogicalPlan::RecursiveQuery(_) => {
588                // This rule handles recursion itself in a `ApplyOrder::TopDown` like
589                // manner. Process uncorrelated subqueries in expressions
590                // (e.g., Expr::ScalarSubquery), then direct children.
591                plan.map_uncorrelated_subqueries(|c| self.rewrite(c, config))?
592                    .transform_sibling(|plan| {
593                        plan.map_children(|c| self.rewrite(c, config))
594                    })?
595            }
596        };
597
598        // If we rewrote the plan, ensure the schema stays the same
599        if optimized_plan.transformed && optimized_plan.data.schema() != &original_schema
600        {
601            optimized_plan.map_data(|optimized_plan| {
602                build_recover_project_plan(&original_schema, optimized_plan)
603            })
604        } else {
605            Ok(optimized_plan)
606        }
607    }
608
609    fn name(&self) -> &str {
610        "common_sub_expression_eliminate"
611    }
612}
613
614/// Which type of [expressions](Expr) should be considered for rewriting?
615#[derive(Debug, Clone, Copy)]
616enum ExprMask {
617    /// Ignores:
618    ///
619    /// - [`Literal`](Expr::Literal)
620    /// - [`Columns`](Expr::Column)
621    /// - [`ScalarVariable`](Expr::ScalarVariable)
622    /// - [`Alias`](Expr::Alias)
623    /// - [`Wildcard`](Expr::Wildcard)
624    /// - [`AggregateFunction`](Expr::AggregateFunction)
625    Normal,
626
627    /// Like [`Normal`](Self::Normal), but includes [`AggregateFunction`](Expr::AggregateFunction).
628    NormalAndAggregates,
629}
630
631struct ExprCSEController<'a> {
632    alias_generator: &'a AliasGenerator,
633    mask: ExprMask,
634
635    // how many aliases have we seen so far
636    alias_counter: usize,
637}
638
639impl<'a> ExprCSEController<'a> {
640    fn new(alias_generator: &'a AliasGenerator, mask: ExprMask) -> Self {
641        Self {
642            alias_generator,
643            mask,
644            alias_counter: 0,
645        }
646    }
647}
648
649impl CSEController for ExprCSEController<'_> {
650    type Node = Expr;
651
652    fn conditional_children(node: &Expr) -> Option<(Vec<&Expr>, Vec<&Expr>)> {
653        match node {
654            // In case of `ScalarFunction`s and `HigherOrderFunction`s we don't know which children are surely
655            // executed so start visiting all children conditionally and stop the
656            // recursion with `TreeNodeRecursion::Jump`.
657            Expr::ScalarFunction(ScalarFunction { func, args }) => {
658                func.conditional_arguments(args)
659            }
660            Expr::HigherOrderFunction(HigherOrderFunction { func, args }) => {
661                func.conditional_arguments(args)
662            }
663
664            // In case of `And` and `Or` the first child is surely executed, but we
665            // account subexpressions as conditional in the second.
666            Expr::BinaryExpr(BinaryExpr {
667                left,
668                op: Operator::And | Operator::Or,
669                right,
670            }) => Some((vec![left.as_ref()], vec![right.as_ref()])),
671
672            // In case of `Case` the optional base expression and the first when
673            // expressions are surely executed, but we account subexpressions as
674            // conditional in the others.
675            Expr::Case(Case {
676                expr,
677                when_then_expr,
678                else_expr,
679            }) => Some((
680                expr.iter()
681                    .map(|e| e.as_ref())
682                    .chain(when_then_expr.iter().take(1).map(|(when, _)| when.as_ref()))
683                    .collect(),
684                when_then_expr
685                    .iter()
686                    .take(1)
687                    .map(|(_, then)| then.as_ref())
688                    .chain(
689                        when_then_expr
690                            .iter()
691                            .skip(1)
692                            .flat_map(|(when, then)| [when.as_ref(), then.as_ref()]),
693                    )
694                    .chain(else_expr.iter().map(|e| e.as_ref()))
695                    .collect(),
696            )),
697            _ => None,
698        }
699    }
700
701    fn is_valid(node: &Expr) -> bool {
702        !node.is_volatile_node()
703            && !matches!(node, Expr::Lambda(_) | Expr::LambdaVariable(_))
704    }
705
706    fn is_ignored(&self, node: &Expr) -> bool {
707        // MoveTowardsLeafNodes expressions (e.g. get_field) are cheap struct
708        // field accesses that the ExtractLeafExpressions / PushDownLeafProjections
709        // rules deliberately duplicate when needed (one copy for a filter
710        // predicate, another for an output column). CSE deduplicating them
711        // creates intermediate projections that fight with those rules,
712        // causing optimizer instability — ExtractLeafExpressions will undo
713        // the dedup, creating an infinite loop that runs until the iteration
714        // limit is hit. Skip them.
715        if node.placement() == ExpressionPlacement::MoveTowardsLeafNodes {
716            return true;
717        }
718
719        // TODO: remove the next line after `Expr::Wildcard` is removed
720        #[expect(deprecated)]
721        let is_normal_minus_aggregates = matches!(
722            node,
723            // TODO: there's an argument for removing `Literal` from here,
724            // maybe using `Expr::placemement().should_push_to_leaves()` instead
725            // so that we extract common literals and don't broadcast them to num_batch_rows multiple times.
726            // However that currently breaks things like `percentile_cont()` which expect literal arguments
727            // (and would instead be getting `col(__common_expr_n)`).
728            Expr::Literal(..)
729                | Expr::Column(..)
730                | Expr::ScalarVariable(..)
731                | Expr::Alias(..)
732                | Expr::Wildcard { .. }
733                | Expr::Lambda(_)
734                | Expr::LambdaVariable(_)
735        );
736
737        let is_aggr = matches!(node, Expr::AggregateFunction(..));
738
739        match self.mask {
740            ExprMask::Normal => is_normal_minus_aggregates || is_aggr,
741            ExprMask::NormalAndAggregates => is_normal_minus_aggregates,
742        }
743    }
744
745    fn generate_alias(&self) -> String {
746        self.alias_generator.next(CSE_PREFIX)
747    }
748
749    fn rewrite(&mut self, node: &Self::Node, alias: &str) -> Self::Node {
750        // alias the expressions without an `Alias` ancestor node
751        if self.alias_counter > 0 {
752            col(alias)
753        } else {
754            self.alias_counter += 1;
755            col(alias).alias(node.schema_name().to_string())
756        }
757    }
758
759    fn rewrite_f_down(&mut self, node: &Expr) {
760        if matches!(node, Expr::Alias(_)) {
761            self.alias_counter += 1;
762        }
763    }
764    fn rewrite_f_up(&mut self, node: &Expr) {
765        if matches!(node, Expr::Alias(_)) {
766            self.alias_counter -= 1
767        }
768    }
769}
770
771impl Default for CommonSubexprEliminate {
772    fn default() -> Self {
773        Self::new()
774    }
775}
776
777/// Build the "intermediate" projection plan that evaluates the extracted common
778/// expressions.
779///
780/// # Arguments
781/// input: the input plan
782///
783/// common_exprs: which common subexpressions were used (and thus are added to
784/// intermediate projection)
785///
786/// expr_stats: the set of common subexpressions
787fn build_common_expr_project_plan(
788    input: LogicalPlan,
789    common_exprs: Vec<(Expr, String)>,
790) -> Result<LogicalPlan> {
791    let mut fields_set = BTreeSet::new();
792    let mut project_exprs = common_exprs
793        .into_iter()
794        .map(|(expr, expr_alias)| {
795            fields_set.insert(expr_alias.clone());
796            Ok(expr.alias(expr_alias))
797        })
798        .collect::<Result<Vec<_>>>()?;
799
800    for (qualifier, field) in input.schema().iter() {
801        if fields_set.insert(qualified_name(qualifier, field.name())) {
802            project_exprs.push(Expr::from((qualifier, field)));
803        }
804    }
805
806    Projection::try_new(project_exprs, Arc::new(input)).map(LogicalPlan::Projection)
807}
808
809/// Build the projection plan to eliminate unnecessary columns produced by
810/// the "intermediate" projection plan built in [build_common_expr_project_plan].
811///
812/// This is required to keep the schema the same for plans that pass the input
813/// on to the output, such as `Filter` or `Sort`.
814fn build_recover_project_plan(
815    schema: &DFSchema,
816    input: LogicalPlan,
817) -> Result<LogicalPlan> {
818    let col_exprs = schema.iter().map(Expr::from).collect();
819    Projection::try_new(col_exprs, Arc::new(input)).map(LogicalPlan::Projection)
820}
821
822fn extract_expressions(expr: &Expr, result: &mut Vec<Expr>) {
823    if let Expr::GroupingSet(groupings) = expr {
824        for e in groupings.distinct_expr() {
825            let (qualifier, field_name) = e.qualified_name();
826            let col = Column::new(qualifier, field_name);
827            result.push(Expr::Column(col))
828        }
829        result.push(Expr::Column(Column::from_name(
830            Aggregate::INTERNAL_GROUPING_ID,
831        )));
832    } else {
833        let (qualifier, field_name) = expr.qualified_name();
834        let col = Column::new(qualifier, field_name);
835        result.push(Expr::Column(col));
836    }
837}
838
839#[cfg(test)]
840mod test {
841
842    use std::iter;
843
844    use arrow::datatypes::{DataType, Field, Schema};
845    use datafusion_expr::logical_plan::{JoinType, table_scan};
846    use datafusion_expr::{
847        AccumulatorFactoryFunction, AggregateUDF, ColumnarValue, ScalarFunctionArgs,
848        ScalarUDF, ScalarUDFImpl, Signature, SimpleAggregateUDF, Volatility,
849        grouping_set, is_null, not,
850    };
851    use datafusion_expr::{lit, logical_plan::builder::LogicalPlanBuilder};
852
853    use super::*;
854    use crate::assert_optimized_plan_eq_snapshot;
855    use crate::optimizer::OptimizerContext;
856    use crate::test::udfs::leaf_udf_expr;
857    use crate::test::*;
858    use datafusion_expr::test::function_stub::{avg, sum};
859
860    macro_rules! assert_optimized_plan_equal {
861        (
862            $config:expr,
863            $plan:expr,
864            @ $expected:literal $(,)?
865        ) => {{
866            let rules: Vec<Arc<dyn crate::OptimizerRule + Send + Sync>> = vec![Arc::new(CommonSubexprEliminate::new())];
867            assert_optimized_plan_eq_snapshot!(
868                $config,
869                rules,
870                $plan,
871                @ $expected,
872            )
873        }};
874
875        (
876            $plan:expr,
877            @ $expected:literal $(,)?
878        ) => {{
879            let rules: Vec<Arc<dyn crate::OptimizerRule + Send + Sync>> = vec![Arc::new(CommonSubexprEliminate::new())];
880            let optimizer_ctx = OptimizerContext::new();
881            assert_optimized_plan_eq_snapshot!(
882                optimizer_ctx,
883                rules,
884                $plan,
885                @ $expected,
886            )
887        }};
888    }
889
890    #[test]
891    fn tpch_q1_simplified() -> Result<()> {
892        // SQL:
893        //  select
894        //      sum(a * (1 - b)),
895        //      sum(a * (1 - b) * (1 + c))
896        //  from T;
897        //
898        // The manual assembled logical plan don't contains the outermost `Projection`.
899
900        let table_scan = test_table_scan()?;
901
902        let plan = LogicalPlanBuilder::from(table_scan)
903            .aggregate(
904                iter::empty::<Expr>(),
905                vec![
906                    sum(col("a") * (lit(1) - col("b"))),
907                    sum((col("a") * (lit(1) - col("b"))) * (lit(1) + col("c"))),
908                ],
909            )?
910            .build()?;
911
912        assert_optimized_plan_equal!(
913            plan,
914            @ r"
915        Aggregate: groupBy=[[]], aggr=[[sum(__common_expr_1 AS test.a * Int32(1) - test.b), sum(__common_expr_1 AS test.a * Int32(1) - test.b * (Int32(1) + test.c))]]
916          Projection: test.a * (Int32(1) - test.b) AS __common_expr_1, test.a, test.b, test.c
917            TableScan: test
918        "
919        )
920    }
921
922    #[test]
923    fn nested_aliases() -> Result<()> {
924        let table_scan = test_table_scan()?;
925
926        let plan = LogicalPlanBuilder::from(table_scan)
927            .project(vec![
928                (col("a") + col("b") - col("c")).alias("alias1") * (col("a") + col("b")),
929                col("a") + col("b"),
930            ])?
931            .build()?;
932
933        assert_optimized_plan_equal!(
934            plan,
935            @ r"
936        Projection: __common_expr_1 - test.c AS alias1 * __common_expr_1 AS test.a + test.b, __common_expr_1 AS test.a + test.b
937          Projection: test.a + test.b AS __common_expr_1, test.a, test.b, test.c
938            TableScan: test
939        "
940        )
941    }
942
943    #[test]
944    fn aggregate() -> Result<()> {
945        let table_scan = test_table_scan()?;
946
947        let return_type = DataType::UInt32;
948        let accumulator: AccumulatorFactoryFunction = Arc::new(|_| unimplemented!());
949        let udf_agg = |inner: Expr| {
950            Expr::AggregateFunction(datafusion_expr::expr::AggregateFunction::new_udf(
951                Arc::new(AggregateUDF::from(SimpleAggregateUDF::new_with_signature(
952                    "my_agg",
953                    Signature::exact(vec![DataType::UInt32], Volatility::Stable),
954                    return_type.clone(),
955                    Arc::clone(&accumulator),
956                    vec![Field::new("value", DataType::UInt32, true).into()],
957                ))),
958                vec![inner],
959                false,
960                None,
961                vec![],
962                None,
963            ))
964        };
965
966        // test: common aggregates
967        let plan = LogicalPlanBuilder::from(table_scan.clone())
968            .aggregate(
969                iter::empty::<Expr>(),
970                vec![
971                    // common: avg(col("a"))
972                    avg(col("a")).alias("col1"),
973                    avg(col("a")).alias("col2"),
974                    // no common
975                    avg(col("b")).alias("col3"),
976                    avg(col("c")),
977                    // common: udf_agg(col("a"))
978                    udf_agg(col("a")).alias("col4"),
979                    udf_agg(col("a")).alias("col5"),
980                    // no common
981                    udf_agg(col("b")).alias("col6"),
982                    udf_agg(col("c")),
983                ],
984            )?
985            .build()?;
986
987        assert_optimized_plan_equal!(
988            plan,
989            @ r"
990        Projection: __common_expr_1 AS col1, __common_expr_1 AS col2, col3, __common_expr_3 AS avg(test.c), __common_expr_2 AS col4, __common_expr_2 AS col5, col6, __common_expr_4 AS my_agg(test.c)
991          Aggregate: groupBy=[[]], aggr=[[avg(test.a) AS __common_expr_1, my_agg(test.a) AS __common_expr_2, avg(test.b) AS col3, avg(test.c) AS __common_expr_3, my_agg(test.b) AS col6, my_agg(test.c) AS __common_expr_4]]
992            TableScan: test
993        "
994        )?;
995
996        // test: trafo after aggregate
997        let plan = LogicalPlanBuilder::from(table_scan.clone())
998            .aggregate(
999                iter::empty::<Expr>(),
1000                vec![
1001                    lit(1) + avg(col("a")),
1002                    lit(1) - avg(col("a")),
1003                    lit(1) + udf_agg(col("a")),
1004                    lit(1) - udf_agg(col("a")),
1005                ],
1006            )?
1007            .build()?;
1008
1009        assert_optimized_plan_equal!(
1010            plan,
1011            @ r"
1012        Projection: Int32(1) + __common_expr_1 AS avg(test.a), Int32(1) - __common_expr_1 AS avg(test.a), Int32(1) + __common_expr_2 AS my_agg(test.a), Int32(1) - __common_expr_2 AS my_agg(test.a)
1013          Aggregate: groupBy=[[]], aggr=[[avg(test.a) AS __common_expr_1, my_agg(test.a) AS __common_expr_2]]
1014            TableScan: test
1015        "
1016        )?;
1017
1018        // test: transformation before aggregate
1019        let plan = LogicalPlanBuilder::from(table_scan.clone())
1020            .aggregate(
1021                iter::empty::<Expr>(),
1022                vec![
1023                    avg(lit(1u32) + col("a")).alias("col1"),
1024                    udf_agg(lit(1u32) + col("a")).alias("col2"),
1025                ],
1026            )?
1027            .build()?;
1028
1029        assert_optimized_plan_equal!(
1030            plan,
1031            @ r"
1032        Aggregate: groupBy=[[]], aggr=[[avg(__common_expr_1) AS col1, my_agg(__common_expr_1) AS col2]]
1033          Projection: UInt32(1) + test.a AS __common_expr_1, test.a, test.b, test.c
1034            TableScan: test
1035        "
1036        )?;
1037
1038        // test: common between agg and group
1039        let plan = LogicalPlanBuilder::from(table_scan.clone())
1040            .aggregate(
1041                vec![lit(1u32) + col("a")],
1042                vec![
1043                    avg(lit(1u32) + col("a")).alias("col1"),
1044                    udf_agg(lit(1u32) + col("a")).alias("col2"),
1045                ],
1046            )?
1047            .build()?;
1048
1049        assert_optimized_plan_equal!(
1050            plan,
1051            @ r"
1052        Aggregate: groupBy=[[__common_expr_1 AS UInt32(1) + test.a]], aggr=[[avg(__common_expr_1) AS col1, my_agg(__common_expr_1) AS col2]]
1053          Projection: UInt32(1) + test.a AS __common_expr_1, test.a, test.b, test.c
1054            TableScan: test
1055        "
1056        )?;
1057
1058        // test: all mixed
1059        let plan = LogicalPlanBuilder::from(table_scan)
1060            .aggregate(
1061                vec![lit(1u32) + col("a")],
1062                vec![
1063                    (lit(1u32) + avg(lit(1u32) + col("a"))).alias("col1"),
1064                    (lit(1u32) - avg(lit(1u32) + col("a"))).alias("col2"),
1065                    avg(lit(1u32) + col("a")),
1066                    (lit(1u32) + udf_agg(lit(1u32) + col("a"))).alias("col3"),
1067                    (lit(1u32) - udf_agg(lit(1u32) + col("a"))).alias("col4"),
1068                    udf_agg(lit(1u32) + col("a")),
1069                ],
1070            )?
1071            .build()?;
1072
1073        assert_optimized_plan_equal!(
1074            plan,
1075            @ r"
1076        Projection: UInt32(1) + test.a, UInt32(1) + __common_expr_2 AS col1, UInt32(1) - __common_expr_2 AS col2, __common_expr_4 AS avg(UInt32(1) + test.a), UInt32(1) + __common_expr_3 AS col3, UInt32(1) - __common_expr_3 AS col4, __common_expr_5 AS my_agg(UInt32(1) + test.a)
1077          Aggregate: groupBy=[[__common_expr_1 AS UInt32(1) + test.a]], aggr=[[avg(__common_expr_1) AS __common_expr_2, my_agg(__common_expr_1) AS __common_expr_3, avg(__common_expr_1 AS UInt32(1) + test.a) AS __common_expr_4, my_agg(__common_expr_1 AS UInt32(1) + test.a) AS __common_expr_5]]
1078            Projection: UInt32(1) + test.a AS __common_expr_1, test.a, test.b, test.c
1079              TableScan: test
1080        "
1081        )
1082    }
1083
1084    #[test]
1085    fn aggregate_with_relations_and_dots() -> Result<()> {
1086        let schema = Schema::new(vec![Field::new("col.a", DataType::UInt32, false)]);
1087        let table_scan = table_scan(Some("table.test"), &schema, None)?.build()?;
1088
1089        let col_a = Expr::Column(Column::new(Some("table.test"), "col.a"));
1090
1091        let plan = LogicalPlanBuilder::from(table_scan)
1092            .aggregate(
1093                vec![col_a.clone()],
1094                vec![
1095                    (lit(1u32) + avg(lit(1u32) + col_a.clone())),
1096                    avg(lit(1u32) + col_a),
1097                ],
1098            )?
1099            .build()?;
1100
1101        assert_optimized_plan_equal!(
1102            plan,
1103            @ r"
1104        Projection: table.test.col.a, UInt32(1) + __common_expr_2 AS avg(UInt32(1) + table.test.col.a), __common_expr_2 AS avg(UInt32(1) + table.test.col.a)
1105          Aggregate: groupBy=[[table.test.col.a]], aggr=[[avg(__common_expr_1 AS UInt32(1) + table.test.col.a) AS __common_expr_2]]
1106            Projection: UInt32(1) + table.test.col.a AS __common_expr_1, table.test.col.a
1107              TableScan: table.test
1108        "
1109        )
1110    }
1111
1112    #[test]
1113    fn common_aggregate_grouping_set_preserves_internal_id() -> Result<()> {
1114        let plan = LogicalPlanBuilder::from(test_table_scan()?)
1115            .aggregate(
1116                vec![grouping_set(vec![vec![col("a")]])],
1117                vec![avg(col("b")).alias("first"), avg(col("b")).alias("second")],
1118            )?
1119            .filter(col(Aggregate::INTERNAL_GROUPING_ID).eq(lit(0_u8)))?
1120            .build()?;
1121
1122        assert_optimized_plan_equal!(
1123            plan,
1124            @ r"
1125        Filter: __grouping_id = UInt8(0)
1126          Projection: test.a, __grouping_id, __common_expr_1 AS first, __common_expr_1 AS second
1127            Aggregate: groupBy=[[GROUPING SETS ((test.a))]], aggr=[[avg(test.b) AS __common_expr_1]]
1128              TableScan: test
1129        "
1130        )
1131    }
1132
1133    #[test]
1134    fn subexpr_in_same_order() -> Result<()> {
1135        let table_scan = test_table_scan()?;
1136
1137        let plan = LogicalPlanBuilder::from(table_scan)
1138            .project(vec![
1139                (lit(1) + col("a")).alias("first"),
1140                (lit(1) + col("a")).alias("second"),
1141            ])?
1142            .build()?;
1143
1144        assert_optimized_plan_equal!(
1145            plan,
1146            @ r"
1147        Projection: __common_expr_1 AS first, __common_expr_1 AS second
1148          Projection: Int32(1) + test.a AS __common_expr_1, test.a, test.b, test.c
1149            TableScan: test
1150        "
1151        )
1152    }
1153
1154    #[test]
1155    fn subexpr_in_different_order() -> Result<()> {
1156        let table_scan = test_table_scan()?;
1157
1158        let plan = LogicalPlanBuilder::from(table_scan)
1159            .project(vec![lit(1) + col("a"), col("a") + lit(1)])?
1160            .build()?;
1161
1162        assert_optimized_plan_equal!(
1163            plan,
1164            @ r"
1165        Projection: __common_expr_1 AS Int32(1) + test.a, __common_expr_1 AS test.a + Int32(1)
1166          Projection: Int32(1) + test.a AS __common_expr_1, test.a, test.b, test.c
1167            TableScan: test
1168        "
1169        )
1170    }
1171
1172    #[test]
1173    fn cross_plans_subexpr() -> Result<()> {
1174        let table_scan = test_table_scan()?;
1175
1176        let plan = LogicalPlanBuilder::from(table_scan)
1177            .project(vec![lit(1) + col("a"), col("a")])?
1178            .project(vec![lit(1) + col("a")])?
1179            .build()?;
1180
1181        assert_optimized_plan_equal!(
1182            plan,
1183            @ r"
1184        Projection: Int32(1) + test.a
1185          Projection: Int32(1) + test.a, test.a
1186            TableScan: test
1187        "
1188        )
1189    }
1190
1191    #[test]
1192    fn redundant_project_fields() {
1193        let table_scan = test_table_scan().unwrap();
1194        let c_plus_a = col("c") + col("a");
1195        let b_plus_a = col("b") + col("a");
1196        let common_exprs_1 = vec![
1197            (c_plus_a, format!("{CSE_PREFIX}_1")),
1198            (b_plus_a, format!("{CSE_PREFIX}_2")),
1199        ];
1200        let c_plus_a_2 = col(format!("{CSE_PREFIX}_1"));
1201        let b_plus_a_2 = col(format!("{CSE_PREFIX}_2"));
1202        let common_exprs_2 = vec![
1203            (c_plus_a_2, format!("{CSE_PREFIX}_3")),
1204            (b_plus_a_2, format!("{CSE_PREFIX}_4")),
1205        ];
1206        let project = build_common_expr_project_plan(table_scan, common_exprs_1).unwrap();
1207        let project_2 = build_common_expr_project_plan(project, common_exprs_2).unwrap();
1208
1209        let mut field_set = BTreeSet::new();
1210        for name in project_2.schema().field_names() {
1211            assert!(field_set.insert(name));
1212        }
1213    }
1214
1215    #[test]
1216    fn redundant_project_fields_join_input() {
1217        let table_scan_1 = test_table_scan_with_name("test1").unwrap();
1218        let table_scan_2 = test_table_scan_with_name("test2").unwrap();
1219        let join = LogicalPlanBuilder::from(table_scan_1)
1220            .join(table_scan_2, JoinType::Inner, (vec!["a"], vec!["a"]), None)
1221            .unwrap()
1222            .build()
1223            .unwrap();
1224        let c_plus_a = col("test1.c") + col("test1.a");
1225        let b_plus_a = col("test1.b") + col("test1.a");
1226        let common_exprs_1 = vec![
1227            (c_plus_a, format!("{CSE_PREFIX}_1")),
1228            (b_plus_a, format!("{CSE_PREFIX}_2")),
1229        ];
1230        let c_plus_a_2 = col(format!("{CSE_PREFIX}_1"));
1231        let b_plus_a_2 = col(format!("{CSE_PREFIX}_2"));
1232        let common_exprs_2 = vec![
1233            (c_plus_a_2, format!("{CSE_PREFIX}_3")),
1234            (b_plus_a_2, format!("{CSE_PREFIX}_4")),
1235        ];
1236        let project = build_common_expr_project_plan(join, common_exprs_1).unwrap();
1237        let project_2 = build_common_expr_project_plan(project, common_exprs_2).unwrap();
1238
1239        let mut field_set = BTreeSet::new();
1240        for name in project_2.schema().field_names() {
1241            assert!(field_set.insert(name));
1242        }
1243    }
1244
1245    #[test]
1246    fn eliminated_subexpr_datatype() {
1247        use datafusion_expr::cast;
1248
1249        let schema = Schema::new(vec![
1250            Field::new("a", DataType::UInt64, false),
1251            Field::new("b", DataType::UInt64, false),
1252            Field::new("c", DataType::UInt64, false),
1253        ]);
1254
1255        let plan = table_scan(Some("table"), &schema, None)
1256            .unwrap()
1257            .filter(
1258                cast(col("a"), DataType::Int64)
1259                    .lt(lit(1_i64))
1260                    .and(cast(col("a"), DataType::Int64).not_eq(lit(1_i64))),
1261            )
1262            .unwrap()
1263            .build()
1264            .unwrap();
1265        let rule = CommonSubexprEliminate::new();
1266        let optimized_plan = rule.rewrite(plan, &OptimizerContext::new()).unwrap();
1267        assert!(optimized_plan.transformed);
1268        let optimized_plan = optimized_plan.data;
1269
1270        let schema = optimized_plan.schema();
1271        let fields_with_datatypes: Vec<_> = schema
1272            .fields()
1273            .iter()
1274            .map(|field| (field.name(), field.data_type()))
1275            .collect();
1276        let formatted_fields_with_datatype = format!("{fields_with_datatypes:#?}");
1277        let expected = r#"[
1278    (
1279        "a",
1280        UInt64,
1281    ),
1282    (
1283        "b",
1284        UInt64,
1285    ),
1286    (
1287        "c",
1288        UInt64,
1289    ),
1290]"#;
1291        assert_eq!(expected, formatted_fields_with_datatype);
1292    }
1293
1294    #[test]
1295    fn filter_schema_changed() -> Result<()> {
1296        let table_scan = test_table_scan()?;
1297
1298        let plan = LogicalPlanBuilder::from(table_scan)
1299            .filter((lit(1) + col("a") - lit(10)).gt(lit(1) + col("a")))?
1300            .build()?;
1301
1302        assert_optimized_plan_equal!(
1303            plan,
1304            @ r"
1305        Projection: test.a, test.b, test.c
1306          Filter: __common_expr_1 - Int32(10) > __common_expr_1
1307            Projection: Int32(1) + test.a AS __common_expr_1, test.a, test.b, test.c
1308              TableScan: test
1309        "
1310        )
1311    }
1312
1313    #[test]
1314    fn test_extract_expressions_from_grouping_set() -> Result<()> {
1315        let mut result = Vec::with_capacity(4);
1316        let grouping = grouping_set(vec![vec![col("a"), col("b")], vec![col("c")]]);
1317        extract_expressions(&grouping, &mut result);
1318
1319        assert_eq!(
1320            result,
1321            vec![
1322                col("a"),
1323                col("b"),
1324                col("c"),
1325                col(Aggregate::INTERNAL_GROUPING_ID),
1326            ]
1327        );
1328        Ok(())
1329    }
1330
1331    #[test]
1332    fn test_extract_expressions_from_grouping_set_with_identical_expr() -> Result<()> {
1333        let mut result = Vec::with_capacity(3);
1334        let grouping = grouping_set(vec![vec![col("a"), col("b")], vec![col("a")]]);
1335        extract_expressions(&grouping, &mut result);
1336        assert_eq!(
1337            result,
1338            vec![col("a"), col("b"), col(Aggregate::INTERNAL_GROUPING_ID),]
1339        );
1340        Ok(())
1341    }
1342
1343    #[test]
1344    fn test_alias_collision() -> Result<()> {
1345        let table_scan = test_table_scan()?;
1346
1347        let config = OptimizerContext::new();
1348        let common_expr_1 = config.alias_generator().next(CSE_PREFIX);
1349        let plan = LogicalPlanBuilder::from(table_scan.clone())
1350            .project(vec![
1351                (col("a") + col("b")).alias(common_expr_1.clone()),
1352                col("c"),
1353            ])?
1354            .project(vec![
1355                col(common_expr_1.clone()).alias("c1"),
1356                col(common_expr_1).alias("c2"),
1357                (col("c") + lit(2)).alias("c3"),
1358                (col("c") + lit(2)).alias("c4"),
1359            ])?
1360            .build()?;
1361
1362        assert_optimized_plan_equal!(
1363            config,
1364            plan,
1365            @ r"
1366        Projection: __common_expr_1 AS c1, __common_expr_1 AS c2, __common_expr_2 AS c3, __common_expr_2 AS c4
1367          Projection: test.c + Int32(2) AS __common_expr_2, __common_expr_1, test.c
1368            Projection: test.a + test.b AS __common_expr_1, test.c
1369              TableScan: test
1370        "
1371        )?;
1372
1373        let config = OptimizerContext::new();
1374        let _common_expr_1 = config.alias_generator().next(CSE_PREFIX);
1375        let common_expr_2 = config.alias_generator().next(CSE_PREFIX);
1376        let plan = LogicalPlanBuilder::from(table_scan)
1377            .project(vec![
1378                (col("a") + col("b")).alias(common_expr_2.clone()),
1379                col("c"),
1380            ])?
1381            .project(vec![
1382                col(common_expr_2.clone()).alias("c1"),
1383                col(common_expr_2).alias("c2"),
1384                (col("c") + lit(2)).alias("c3"),
1385                (col("c") + lit(2)).alias("c4"),
1386            ])?
1387            .build()?;
1388
1389        assert_optimized_plan_equal!(
1390            config,
1391            plan,
1392            @ r"
1393        Projection: __common_expr_2 AS c1, __common_expr_2 AS c2, __common_expr_3 AS c3, __common_expr_3 AS c4
1394          Projection: test.c + Int32(2) AS __common_expr_3, __common_expr_2, test.c
1395            Projection: test.a + test.b AS __common_expr_2, test.c
1396              TableScan: test
1397        "
1398        )?;
1399
1400        Ok(())
1401    }
1402
1403    #[test]
1404    fn test_extract_expressions_from_col() -> Result<()> {
1405        let mut result = Vec::with_capacity(1);
1406        extract_expressions(&col("a"), &mut result);
1407        assert!(result.len() == 1);
1408        Ok(())
1409    }
1410
1411    #[test]
1412    fn test_short_circuits() -> Result<()> {
1413        let table_scan = test_table_scan()?;
1414
1415        let extracted_short_circuit = col("a").eq(lit(0)).or(col("b").eq(lit(0)));
1416        let extracted_short_circuit_leg_1 = (col("a") + col("b")).eq(lit(0));
1417        let not_extracted_short_circuit_leg_2 = (col("a") - col("b")).eq(lit(0));
1418        let extracted_short_circuit_leg_3 = (col("a") * col("b")).eq(lit(0));
1419        let plan = LogicalPlanBuilder::from(table_scan)
1420            .project(vec![
1421                extracted_short_circuit.clone().alias("c1"),
1422                extracted_short_circuit.alias("c2"),
1423                extracted_short_circuit_leg_1
1424                    .clone()
1425                    .or(not_extracted_short_circuit_leg_2.clone())
1426                    .alias("c3"),
1427                extracted_short_circuit_leg_1
1428                    .and(not_extracted_short_circuit_leg_2)
1429                    .alias("c4"),
1430                extracted_short_circuit_leg_3
1431                    .clone()
1432                    .or(extracted_short_circuit_leg_3)
1433                    .alias("c5"),
1434            ])?
1435            .build()?;
1436
1437        assert_optimized_plan_equal!(
1438            plan,
1439            @ r"
1440        Projection: __common_expr_1 AS c1, __common_expr_1 AS c2, __common_expr_2 OR test.a - test.b = Int32(0) AS c3, __common_expr_2 AND test.a - test.b = Int32(0) AS c4, __common_expr_3 OR __common_expr_3 AS c5
1441          Projection: test.a = Int32(0) OR test.b = Int32(0) AS __common_expr_1, test.a + test.b = Int32(0) AS __common_expr_2, test.a * test.b = Int32(0) AS __common_expr_3, test.a, test.b, test.c
1442            TableScan: test
1443        "
1444        )
1445    }
1446
1447    #[test]
1448    fn test_volatile() -> Result<()> {
1449        let table_scan = test_table_scan()?;
1450
1451        let extracted_child = col("a") + col("b");
1452        let rand = rand_func().call(vec![]);
1453        let not_extracted_volatile = extracted_child + rand;
1454        let plan = LogicalPlanBuilder::from(table_scan)
1455            .project(vec![
1456                not_extracted_volatile.clone().alias("c1"),
1457                not_extracted_volatile.alias("c2"),
1458            ])?
1459            .build()?;
1460
1461        assert_optimized_plan_equal!(
1462            plan,
1463            @ r"
1464        Projection: __common_expr_1 + random() AS c1, __common_expr_1 + random() AS c2
1465          Projection: test.a + test.b AS __common_expr_1, test.a, test.b, test.c
1466            TableScan: test
1467        "
1468        )
1469    }
1470
1471    #[test]
1472    fn test_volatile_short_circuits() -> Result<()> {
1473        let table_scan = test_table_scan()?;
1474
1475        let rand = rand_func().call(vec![]);
1476        let extracted_short_circuit_leg_1 = col("a").eq(lit(0));
1477        let not_extracted_volatile_short_circuit_1 =
1478            extracted_short_circuit_leg_1.or(rand.clone().eq(lit(0)));
1479        let not_extracted_short_circuit_leg_2 = col("b").eq(lit(0));
1480        let not_extracted_volatile_short_circuit_2 =
1481            rand.eq(lit(0)).or(not_extracted_short_circuit_leg_2);
1482        let plan = LogicalPlanBuilder::from(table_scan)
1483            .project(vec![
1484                not_extracted_volatile_short_circuit_1.clone().alias("c1"),
1485                not_extracted_volatile_short_circuit_1.alias("c2"),
1486                not_extracted_volatile_short_circuit_2.clone().alias("c3"),
1487                not_extracted_volatile_short_circuit_2.alias("c4"),
1488            ])?
1489            .build()?;
1490
1491        assert_optimized_plan_equal!(
1492            plan,
1493            @ r"
1494        Projection: __common_expr_1 OR random() = Int32(0) AS c1, __common_expr_1 OR random() = Int32(0) AS c2, random() = Int32(0) OR test.b = Int32(0) AS c3, random() = Int32(0) OR test.b = Int32(0) AS c4
1495          Projection: test.a = Int32(0) AS __common_expr_1, test.a, test.b, test.c
1496            TableScan: test
1497        "
1498        )
1499    }
1500
1501    #[test]
1502    fn test_non_top_level_common_expression() -> Result<()> {
1503        let table_scan = test_table_scan()?;
1504
1505        let common_expr = col("a") + col("b");
1506        let plan = LogicalPlanBuilder::from(table_scan)
1507            .project(vec![
1508                common_expr.clone().alias("c1"),
1509                common_expr.alias("c2"),
1510            ])?
1511            .project(vec![col("c1"), col("c2")])?
1512            .build()?;
1513
1514        assert_optimized_plan_equal!(
1515            plan,
1516            @ r"
1517        Projection: c1, c2
1518          Projection: __common_expr_1 AS c1, __common_expr_1 AS c2
1519            Projection: test.a + test.b AS __common_expr_1, test.a, test.b, test.c
1520              TableScan: test
1521        "
1522        )
1523    }
1524
1525    #[test]
1526    fn test_nested_common_expression() -> Result<()> {
1527        let table_scan = test_table_scan()?;
1528
1529        let nested_common_expr = col("a") + col("b");
1530        let common_expr = nested_common_expr.clone() * nested_common_expr;
1531        let plan = LogicalPlanBuilder::from(table_scan)
1532            .project(vec![
1533                common_expr.clone().alias("c1"),
1534                common_expr.alias("c2"),
1535            ])?
1536            .build()?;
1537
1538        assert_optimized_plan_equal!(
1539            plan,
1540            @ r"
1541        Projection: __common_expr_1 AS c1, __common_expr_1 AS c2
1542          Projection: __common_expr_2 * __common_expr_2 AS __common_expr_1, test.a, test.b, test.c
1543            Projection: test.a + test.b AS __common_expr_2, test.a, test.b, test.c
1544              TableScan: test
1545        "
1546        )
1547    }
1548
1549    #[test]
1550    fn test_normalize_add_expression() -> Result<()> {
1551        // a + b <=> b + a
1552        let table_scan = test_table_scan()?;
1553        let expr = ((col("a") + col("b")) * (col("b") + col("a"))).eq(lit(30));
1554        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1555
1556        assert_optimized_plan_equal!(
1557            plan,
1558            @ r"
1559        Projection: test.a, test.b, test.c
1560          Filter: __common_expr_1 * __common_expr_1 = Int32(30)
1561            Projection: test.a + test.b AS __common_expr_1, test.a, test.b, test.c
1562              TableScan: test
1563        "
1564        )
1565    }
1566
1567    #[test]
1568    fn test_normalize_multi_expression() -> Result<()> {
1569        // a * b <=> b * a
1570        let table_scan = test_table_scan()?;
1571        let expr = ((col("a") * col("b")) + (col("b") * col("a"))).eq(lit(30));
1572        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1573
1574        assert_optimized_plan_equal!(
1575            plan,
1576            @ r"
1577        Projection: test.a, test.b, test.c
1578          Filter: __common_expr_1 + __common_expr_1 = Int32(30)
1579            Projection: test.a * test.b AS __common_expr_1, test.a, test.b, test.c
1580              TableScan: test
1581        "
1582        )
1583    }
1584
1585    #[test]
1586    fn test_normalize_bitset_and_expression() -> Result<()> {
1587        // a & b <=> b & a
1588        let table_scan = test_table_scan()?;
1589        let expr = ((col("a") & col("b")) + (col("b") & col("a"))).eq(lit(30));
1590        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1591
1592        assert_optimized_plan_equal!(
1593            plan,
1594            @ r"
1595        Projection: test.a, test.b, test.c
1596          Filter: __common_expr_1 + __common_expr_1 = Int32(30)
1597            Projection: test.a & test.b AS __common_expr_1, test.a, test.b, test.c
1598              TableScan: test
1599        "
1600        )
1601    }
1602
1603    #[test]
1604    fn test_normalize_bitset_or_expression() -> Result<()> {
1605        // a | b <=> b | a
1606        let table_scan = test_table_scan()?;
1607        let expr = ((col("a") | col("b")) + (col("b") | col("a"))).eq(lit(30));
1608        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1609
1610        assert_optimized_plan_equal!(
1611            plan,
1612            @ r"
1613        Projection: test.a, test.b, test.c
1614          Filter: __common_expr_1 + __common_expr_1 = Int32(30)
1615            Projection: test.a | test.b AS __common_expr_1, test.a, test.b, test.c
1616              TableScan: test
1617        "
1618        )
1619    }
1620
1621    #[test]
1622    fn test_normalize_bitset_xor_expression() -> Result<()> {
1623        // a # b <=> b # a
1624        let table_scan = test_table_scan()?;
1625        let expr = ((col("a") ^ col("b")) + (col("b") ^ col("a"))).eq(lit(30));
1626        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1627
1628        assert_optimized_plan_equal!(
1629            plan,
1630            @ r"
1631        Projection: test.a, test.b, test.c
1632          Filter: __common_expr_1 + __common_expr_1 = Int32(30)
1633            Projection: test.a BIT_XOR test.b AS __common_expr_1, test.a, test.b, test.c
1634              TableScan: test
1635        "
1636        )
1637    }
1638
1639    #[test]
1640    fn test_normalize_eq_expression() -> Result<()> {
1641        // a = b <=> b = a
1642        let table_scan = test_table_scan()?;
1643        let expr = (col("a").eq(col("b"))).and(col("b").eq(col("a")));
1644        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1645
1646        assert_optimized_plan_equal!(
1647            plan,
1648            @ r"
1649        Projection: test.a, test.b, test.c
1650          Filter: __common_expr_1 AND __common_expr_1
1651            Projection: test.a = test.b AS __common_expr_1, test.a, test.b, test.c
1652              TableScan: test
1653        "
1654        )
1655    }
1656
1657    #[test]
1658    fn test_normalize_ne_expression() -> Result<()> {
1659        // a != b <=> b != a
1660        let table_scan = test_table_scan()?;
1661        let expr = (col("a").not_eq(col("b"))).and(col("b").not_eq(col("a")));
1662        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1663
1664        assert_optimized_plan_equal!(
1665            plan,
1666            @ r"
1667        Projection: test.a, test.b, test.c
1668          Filter: __common_expr_1 AND __common_expr_1
1669            Projection: test.a != test.b AS __common_expr_1, test.a, test.b, test.c
1670              TableScan: test
1671        "
1672        )
1673    }
1674
1675    #[test]
1676    fn test_normalize_complex_expression() -> Result<()> {
1677        // case1: a + b * c <=> b * c + a
1678        let table_scan = test_table_scan()?;
1679        let expr = ((col("a") + col("b") * col("c")) - (col("b") * col("c") + col("a")))
1680            .eq(lit(30));
1681        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1682
1683        assert_optimized_plan_equal!(
1684            plan,
1685            @ r"
1686        Projection: test.a, test.b, test.c
1687          Filter: __common_expr_1 - __common_expr_1 = Int32(30)
1688            Projection: test.a + test.b * test.c AS __common_expr_1, test.a, test.b, test.c
1689              TableScan: test
1690        "
1691        )?;
1692
1693        // ((c1 + c2 / c3) * c3 <=> c3 * (c2 / c3 + c1))
1694        let table_scan = test_table_scan()?;
1695        let expr = (((col("a") + col("b") / col("c")) * col("c"))
1696            / (col("c") * (col("b") / col("c") + col("a")))
1697            + col("a"))
1698        .eq(lit(30));
1699        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1700
1701        assert_optimized_plan_equal!(
1702            plan,
1703            @ r"
1704        Projection: test.a, test.b, test.c
1705          Filter: __common_expr_1 / __common_expr_1 + test.a = Int32(30)
1706            Projection: (test.a + test.b / test.c) * test.c AS __common_expr_1, test.a, test.b, test.c
1707              TableScan: test
1708        "
1709        )?;
1710
1711        // c2 / (c1 + c3) <=> c2 / (c3 + c1)
1712        let table_scan = test_table_scan()?;
1713        let expr = ((col("b") / (col("a") + col("c")))
1714            * (col("b") / (col("c") + col("a"))))
1715        .eq(lit(30));
1716        let plan = LogicalPlanBuilder::from(table_scan).filter(expr)?.build()?;
1717        assert_optimized_plan_equal!(
1718            plan,
1719            @ r"
1720        Projection: test.a, test.b, test.c
1721          Filter: __common_expr_1 * __common_expr_1 = Int32(30)
1722            Projection: test.b / (test.a + test.c) AS __common_expr_1, test.a, test.b, test.c
1723              TableScan: test
1724        "
1725        )?;
1726
1727        Ok(())
1728    }
1729
1730    #[derive(Debug, PartialEq, Eq, Hash)]
1731    pub struct TestUdf {
1732        signature: Signature,
1733    }
1734
1735    impl TestUdf {
1736        pub fn new() -> Self {
1737            Self {
1738                signature: Signature::numeric(1, Volatility::Immutable),
1739            }
1740        }
1741    }
1742
1743    impl ScalarUDFImpl for TestUdf {
1744        fn name(&self) -> &str {
1745            "my_udf"
1746        }
1747
1748        fn signature(&self) -> &Signature {
1749            &self.signature
1750        }
1751
1752        fn return_type(&self, _: &[DataType]) -> Result<DataType> {
1753            Ok(DataType::Int32)
1754        }
1755
1756        fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
1757            panic!("not implemented")
1758        }
1759    }
1760
1761    #[test]
1762    fn test_normalize_inner_binary_expression() -> Result<()> {
1763        // Not(a == b) <=> Not(b == a)
1764        let table_scan = test_table_scan()?;
1765        let expr1 = not(col("a").eq(col("b")));
1766        let expr2 = not(col("b").eq(col("a")));
1767        let plan = LogicalPlanBuilder::from(table_scan)
1768            .project(vec![expr1, expr2])?
1769            .build()?;
1770        assert_optimized_plan_equal!(
1771            plan,
1772            @ r"
1773        Projection: __common_expr_1 AS NOT test.a = test.b, __common_expr_1 AS NOT test.b = test.a
1774          Projection: NOT test.a = test.b AS __common_expr_1, test.a, test.b, test.c
1775            TableScan: test
1776        "
1777        )?;
1778
1779        // is_null(a == b) <=> is_null(b == a)
1780        let table_scan = test_table_scan()?;
1781        let expr1 = is_null(col("a").eq(col("b")));
1782        let expr2 = is_null(col("b").eq(col("a")));
1783        let plan = LogicalPlanBuilder::from(table_scan)
1784            .project(vec![expr1, expr2])?
1785            .build()?;
1786        assert_optimized_plan_equal!(
1787            plan,
1788            @ r"
1789        Projection: __common_expr_1 AS test.a = test.b IS NULL, __common_expr_1 AS test.b = test.a IS NULL
1790          Projection: test.a = test.b IS NULL AS __common_expr_1, test.a, test.b, test.c
1791            TableScan: test
1792        "
1793        )?;
1794
1795        // a + b between 0 and 10 <=> b + a between 0 and 10
1796        let table_scan = test_table_scan()?;
1797        let expr1 = (col("a") + col("b")).between(lit(0), lit(10));
1798        let expr2 = (col("b") + col("a")).between(lit(0), lit(10));
1799        let plan = LogicalPlanBuilder::from(table_scan)
1800            .project(vec![expr1, expr2])?
1801            .build()?;
1802        assert_optimized_plan_equal!(
1803            plan,
1804            @ r"
1805        Projection: __common_expr_1 AS test.a + test.b BETWEEN Int32(0) AND Int32(10), __common_expr_1 AS test.b + test.a BETWEEN Int32(0) AND Int32(10)
1806          Projection: test.a + test.b BETWEEN Int32(0) AND Int32(10) AS __common_expr_1, test.a, test.b, test.c
1807            TableScan: test
1808        "
1809        )?;
1810
1811        // c between a + b and 10 <=> c between b + a and 10
1812        let table_scan = test_table_scan()?;
1813        let expr1 = col("c").between(col("a") + col("b"), lit(10));
1814        let expr2 = col("c").between(col("b") + col("a"), lit(10));
1815        let plan = LogicalPlanBuilder::from(table_scan)
1816            .project(vec![expr1, expr2])?
1817            .build()?;
1818        assert_optimized_plan_equal!(
1819            plan,
1820            @ r"
1821        Projection: __common_expr_1 AS test.c BETWEEN test.a + test.b AND Int32(10), __common_expr_1 AS test.c BETWEEN test.b + test.a AND Int32(10)
1822          Projection: test.c BETWEEN test.a + test.b AND Int32(10) AS __common_expr_1, test.a, test.b, test.c
1823            TableScan: test
1824        "
1825        )?;
1826
1827        // function call with argument <=> function call with argument
1828        let udf = ScalarUDF::from(TestUdf::new());
1829        let table_scan = test_table_scan()?;
1830        let expr1 = udf.call(vec![col("a") + col("b")]);
1831        let expr2 = udf.call(vec![col("b") + col("a")]);
1832        let plan = LogicalPlanBuilder::from(table_scan)
1833            .project(vec![expr1, expr2])?
1834            .build()?;
1835        assert_optimized_plan_equal!(
1836            plan,
1837            @ r"
1838        Projection: __common_expr_1 AS my_udf(test.a + test.b), __common_expr_1 AS my_udf(test.b + test.a)
1839          Projection: my_udf(test.a + test.b) AS __common_expr_1, test.a, test.b, test.c
1840            TableScan: test
1841        "
1842        )
1843    }
1844
1845    /// returns a "random" function that is marked volatile (aka each invocation
1846    /// returns a different value)
1847    ///
1848    /// Does not use datafusion_functions::rand to avoid introducing a
1849    /// dependency on that crate.
1850    fn rand_func() -> ScalarUDF {
1851        ScalarUDF::new_from_impl(RandomStub::new())
1852    }
1853
1854    #[derive(Debug, PartialEq, Eq, Hash)]
1855    struct RandomStub {
1856        signature: Signature,
1857    }
1858
1859    impl RandomStub {
1860        fn new() -> Self {
1861            Self {
1862                signature: Signature::exact(vec![], Volatility::Volatile),
1863            }
1864        }
1865    }
1866    impl ScalarUDFImpl for RandomStub {
1867        fn name(&self) -> &str {
1868            "random"
1869        }
1870
1871        fn signature(&self) -> &Signature {
1872            &self.signature
1873        }
1874
1875        fn return_type(&self, _arg_types: &[DataType]) -> Result<DataType> {
1876            Ok(DataType::Float64)
1877        }
1878
1879        fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
1880            panic!("dummy - not implemented")
1881        }
1882    }
1883
1884    /// Identical MoveTowardsLeafNodes expressions should NOT be deduplicated
1885    /// by CSE — they are cheap (e.g. struct field access) and the extraction
1886    /// rules deliberately duplicate them. Deduplicating causes optimizer
1887    /// instability where one optimizer rule will undo the work of another,
1888    /// resulting in an infinite optimization loop until the
1889    /// we hit the max iteration limit and then give up.
1890    #[test]
1891    fn test_leaf_expression_not_extracted() -> Result<()> {
1892        let table_scan = test_table_scan()?;
1893
1894        let leaf = leaf_udf_expr(col("a"));
1895        let plan = LogicalPlanBuilder::from(table_scan)
1896            .project(vec![leaf.clone().alias("c1"), leaf.alias("c2")])?
1897            .build()?;
1898
1899        // Plan should be unchanged — no __common_expr introduced
1900        assert_optimized_plan_equal!(
1901            plan,
1902            @r"
1903        Projection: leaf_udf(test.a) AS c1, leaf_udf(test.a) AS c2
1904          TableScan: test
1905        "
1906        )
1907    }
1908
1909    /// When a MoveTowardsLeafNodes expression appears as a sub-expression of
1910    /// a larger expression that IS duplicated, only the outer expression gets
1911    /// deduplicated; the leaf sub-expression stays inline.
1912    #[test]
1913    fn test_leaf_subexpression_not_extracted() -> Result<()> {
1914        let table_scan = test_table_scan()?;
1915
1916        // leaf_udf(a) + b appears twice — the outer `+` is a common
1917        // sub-expression, but leaf_udf(a) by itself is MoveTowardsLeafNodes
1918        // and should not be extracted separately.
1919        let common = leaf_udf_expr(col("a")) + col("b");
1920        let plan = LogicalPlanBuilder::from(table_scan)
1921            .project(vec![common.clone().alias("c1"), common.alias("c2")])?
1922            .build()?;
1923
1924        // The whole `leaf_udf(a) + b` gets deduplicated as __common_expr_1,
1925        // but leaf_udf(a) alone is NOT pulled out.
1926        assert_optimized_plan_equal!(
1927            plan,
1928            @r"
1929        Projection: __common_expr_1 AS c1, __common_expr_1 AS c2
1930          Projection: leaf_udf(test.a) + test.b AS __common_expr_1, test.a, test.b, test.c
1931            TableScan: test
1932        "
1933        )
1934    }
1935}