Skip to main content

datafusion_optimizer/optimize_projections/
mod.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//! [`OptimizeProjections`] identifies and eliminates unused columns
19
20mod required_indices;
21
22use crate::optimizer::ApplyOrder;
23use crate::{OptimizerConfig, OptimizerRule};
24use std::sync::Arc;
25
26use datafusion_common::{
27    Column, DFSchema, HashMap, JoinType, Result, assert_eq_or_internal_err,
28    get_required_group_by_exprs_indices, internal_datafusion_err, internal_err,
29};
30use datafusion_expr::expr::Alias;
31use datafusion_expr::{
32    Aggregate, Distinct, EmptyRelation, Expr, Projection, TableScan, Unnest, Window,
33    logical_plan::LogicalPlan,
34};
35
36use crate::optimize_projections::required_indices::RequiredIndices;
37use crate::utils::NamePreserver;
38use datafusion_common::tree_node::{
39    Transformed, TreeNode, TreeNodeContainer, TreeNodeRecursion,
40};
41
42/// Optimizer rule to prune unnecessary columns from intermediate schemas
43/// inside the [`LogicalPlan`]. This rule:
44/// - Removes unnecessary columns that do not appear at the output and/or are
45///   not used during any computation step.
46/// - Adds projections to decrease table column size before operators that
47///   benefit from a smaller memory footprint at its input.
48/// - Removes unnecessary [`LogicalPlan::Projection`]s from the [`LogicalPlan`].
49///
50/// `OptimizeProjections` is an optimizer rule that identifies and eliminates
51/// columns from a logical plan that are not used by downstream operations.
52/// This can improve query performance and reduce unnecessary data processing.
53///
54/// The rule analyzes the input logical plan, determines the necessary column
55/// indices, and then removes any unnecessary columns. It also removes any
56/// unnecessary projections from the plan tree.
57///
58/// ## Schema, Field Properties, and Metadata Handling
59///
60/// The `OptimizeProjections` rule preserves schema and field metadata in most optimization scenarios:
61///
62/// **Schema-level metadata preservation by plan type**:
63/// - **Window and Aggregate plans**: Schema metadata is preserved
64/// - **Projection plans**: Schema metadata is preserved per [`projection_schema`](datafusion_expr::logical_plan::projection_schema).
65/// - **Other logical plans**: Schema metadata is preserved unless [`LogicalPlan::recompute_schema`]
66///   is called on plan types that drop metadata
67///
68/// **Field-level properties and metadata**: Individual field properties are preserved when fields
69/// are retained in the optimized plan, determined by [`exprlist_to_fields`](datafusion_expr::utils::exprlist_to_fields)
70/// and [`ExprSchemable::to_field`](datafusion_expr::expr_schema::ExprSchemable::to_field).
71///
72/// **Field precedence**: When the same field appears multiple times, the optimizer
73/// maintains one occurrence and removes duplicates (refer to `RequiredIndices::compact()`),
74/// preserving the properties and metadata of that occurrence.
75#[derive(Default, Debug)]
76pub struct OptimizeProjections {}
77
78impl OptimizeProjections {
79    #[expect(missing_docs)]
80    pub fn new() -> Self {
81        Self {}
82    }
83}
84
85impl OptimizerRule for OptimizeProjections {
86    fn name(&self) -> &str {
87        "optimize_projections"
88    }
89
90    fn apply_order(&self) -> Option<ApplyOrder> {
91        None
92    }
93
94    fn supports_rewrite(&self) -> bool {
95        true
96    }
97
98    fn rewrite(
99        &self,
100        plan: LogicalPlan,
101        config: &dyn OptimizerConfig,
102    ) -> Result<Transformed<LogicalPlan>> {
103        // All output fields are necessary:
104        let indices = RequiredIndices::new_for_all_exprs(&plan);
105        optimize_projections(plan, config, indices)
106    }
107}
108
109/// Removes unnecessary columns (e.g. columns that do not appear in the output
110/// schema and/or are not used during any computation step such as expression
111/// evaluation) from the logical plan and its inputs.
112///
113/// # Parameters
114///
115/// - `plan`: A reference to the input `LogicalPlan` to optimize.
116/// - `config`: A reference to the optimizer configuration.
117/// - `indices`: A slice of column indices that represent the necessary column
118///   indices for downstream (parent) plan nodes.
119///
120/// # Returns
121///
122/// A `Result` object with the following semantics:
123///
124/// - `Ok(Some(LogicalPlan))`: An optimized `LogicalPlan` without unnecessary
125///   columns.
126/// - `Ok(None)`: Signal that the given logical plan did not require any change.
127/// - `Err(error)`: An error occurred during the optimization process.
128#[cfg_attr(feature = "recursive_protection", recursive::recursive)]
129fn optimize_projections(
130    plan: LogicalPlan,
131    config: &dyn OptimizerConfig,
132    indices: RequiredIndices,
133) -> Result<Transformed<LogicalPlan>> {
134    // Recursively rewrite any nodes that may be able to avoid computation given
135    // their parents' required indices.
136    match plan {
137        LogicalPlan::Projection(proj) => {
138            return merge_consecutive_projections(proj)?
139                .transform_data(|proj| {
140                    rewrite_projection_given_requirements(proj, config, &indices)
141                })?
142                .transform_data(|plan| optimize_subqueries(plan, config));
143        }
144        LogicalPlan::Aggregate(aggregate) => {
145            // Split parent requirements to GROUP BY and aggregate sections:
146            let n_group_exprs = aggregate.group_expr_len()?;
147            // Offset aggregate indices so that they point to valid indices at
148            // `aggregate.aggr_expr`:
149            let (group_by_reqs, aggregate_reqs) = indices.split_off(n_group_exprs);
150
151            // Get absolutely necessary GROUP BY fields.
152            //
153            // When the input has no functional dependencies, we can
154            // short-circuit this analysis.
155            let new_group_bys = if aggregate
156                .input
157                .schema()
158                .functional_dependencies()
159                .is_empty()
160            {
161                aggregate.group_expr
162            } else {
163                let group_by_expr_existing = aggregate
164                    .group_expr
165                    .iter()
166                    .map(|group_by_expr| group_by_expr.schema_name().to_string())
167                    .collect::<Vec<_>>();
168
169                if let Some(simplest_groupby_indices) =
170                    get_required_group_by_exprs_indices(
171                        aggregate.input.schema(),
172                        &group_by_expr_existing,
173                    )
174                {
175                    // Some of the fields in the GROUP BY may be required by
176                    // the parent even if these fields are unnecessary in
177                    // terms of functional dependency.
178                    group_by_reqs
179                        .append(&simplest_groupby_indices)
180                        .get_at_indices(&aggregate.group_expr)
181                } else {
182                    aggregate.group_expr
183                }
184            };
185
186            // Only use the absolutely necessary aggregate expressions required
187            // by the parent:
188            let new_aggr_expr = aggregate_reqs.get_at_indices(&aggregate.aggr_expr);
189
190            if new_group_bys.is_empty() && new_aggr_expr.is_empty() {
191                // Global aggregation with no aggregate functions always produces 1 row and no columns.
192                return Ok(Transformed::yes(LogicalPlan::EmptyRelation(
193                    EmptyRelation {
194                        produce_one_row: true,
195                        schema: Arc::new(DFSchema::empty()),
196                    },
197                )));
198            }
199
200            let all_exprs_iter = new_group_bys.iter().chain(new_aggr_expr.iter());
201            let schema = aggregate.input.schema();
202            let necessary_indices =
203                RequiredIndices::new().with_exprs(schema, all_exprs_iter);
204            let necessary_exprs = necessary_indices.get_required_exprs(schema);
205
206            return optimize_projections(
207                Arc::unwrap_or_clone(aggregate.input),
208                config,
209                necessary_indices,
210            )?
211            .transform_data(|aggregate_input| {
212                // Simplify the input of the aggregation by adding a projection so
213                // that its input only contains absolutely necessary columns for
214                // the aggregate expressions. Note that necessary_indices refer to
215                // fields in `aggregate.input.schema()`.
216                add_projection_on_top_if_helpful(aggregate_input, necessary_exprs)
217            })?
218            .map_data(|aggregate_input| {
219                // Create a new aggregate plan with the updated input and only the
220                // absolutely necessary fields:
221                Aggregate::try_new(
222                    Arc::new(aggregate_input),
223                    new_group_bys,
224                    new_aggr_expr,
225                )
226                .map(LogicalPlan::Aggregate)
227            })?
228            .transform_data(|plan| optimize_subqueries(plan, config));
229        }
230        LogicalPlan::Window(window) => {
231            let input_schema = Arc::clone(window.input.schema());
232            // Split parent requirements to child and window expression sections:
233            let n_input_fields = input_schema.fields().len();
234            // Offset window expression indices so that they point to valid
235            // indices at `window.window_expr`:
236            let (child_reqs, window_reqs) = indices.split_off(n_input_fields);
237
238            // Only use window expressions that are absolutely necessary according
239            // to parent requirements:
240            let new_window_expr = window_reqs.get_at_indices(&window.window_expr);
241
242            // Get all the required column indices at the input, either by the
243            // parent or window expression requirements.
244            let required_indices = child_reqs.with_exprs(&input_schema, &new_window_expr);
245
246            return optimize_projections(
247                Arc::unwrap_or_clone(window.input),
248                config,
249                required_indices.clone(),
250            )?
251            .transform_data(|window_child| {
252                if new_window_expr.is_empty() {
253                    // When no window expression is necessary, use the input directly:
254                    Ok(Transformed::no(window_child))
255                } else {
256                    // Calculate required expressions at the input of the window.
257                    // Please note that we use `input_schema`, because `required_indices`
258                    // refers to that schema
259                    let required_exprs =
260                        required_indices.get_required_exprs(&input_schema);
261                    let window_child =
262                        add_projection_on_top_if_helpful(window_child, required_exprs)?
263                            .data;
264                    Window::try_new(new_window_expr, Arc::new(window_child))
265                        .map(LogicalPlan::Window)
266                        .map(Transformed::yes)
267                }
268            })?
269            .transform_data(|plan| optimize_subqueries(plan, config));
270        }
271        LogicalPlan::TableScan(table_scan) => {
272            let TableScan {
273                table_name,
274                source,
275                projection,
276                filters,
277                fetch,
278                projected_schema: _,
279            } = table_scan;
280
281            // Get indices referred to in the original (schema with all fields)
282            // given projected indices.
283            let projection = match &projection {
284                Some(projection) => indices.into_mapped_indices(|idx| projection[idx]),
285                None => indices.into_inner(),
286            };
287            let new_scan =
288                TableScan::try_new(table_name, source, Some(projection), filters, fetch)?;
289
290            return Transformed::yes(LogicalPlan::TableScan(new_scan))
291                .transform_data(|plan| optimize_subqueries(plan, config));
292        }
293        // Other node types are handled below
294        _ => {}
295    };
296
297    // For other plan node types, calculate indices for columns they use and
298    // try to rewrite their children
299    let mut child_required_indices: Vec<RequiredIndices> = match &plan {
300        LogicalPlan::Sort(_)
301        | LogicalPlan::Filter(_)
302        | LogicalPlan::Repartition(_)
303        | LogicalPlan::Union(_)
304        | LogicalPlan::SubqueryAlias(_)
305        | LogicalPlan::Distinct(Distinct::On(_)) => {
306            // Pass index requirements from the parent as well as column indices
307            // that appear in this plan's expressions to its child. All these
308            // operators benefit from "small" inputs, so the projection_beneficial
309            // flag is `true`.
310            plan.inputs()
311                .into_iter()
312                .map(|input| {
313                    indices
314                        .clone()
315                        .with_projection_beneficial()
316                        .with_plan_exprs(&plan, input.schema())
317                })
318                .collect::<Result<_>>()?
319        }
320        LogicalPlan::Limit(_) => {
321            // Pass index requirements from the parent as well as column indices
322            // that appear in this plan's expressions to its child. These operators
323            // do not benefit from "small" inputs, so the projection_beneficial
324            // flag is `false`.
325            plan.inputs()
326                .into_iter()
327                .map(|input| indices.clone().with_plan_exprs(&plan, input.schema()))
328                .collect::<Result<_>>()?
329        }
330        LogicalPlan::Copy(_)
331        | LogicalPlan::Ddl(_)
332        | LogicalPlan::Dml(_)
333        | LogicalPlan::Explain(_)
334        | LogicalPlan::Analyze(_)
335        | LogicalPlan::Subquery(_)
336        | LogicalPlan::Statement(_)
337        | LogicalPlan::Distinct(Distinct::All(_)) => {
338            // These plans require all their fields, and their children should
339            // be treated as final plans -- otherwise, we may have schema a
340            // mismatch.
341            // TODO: For some subquery variants (e.g. a subquery arising from an
342            //       EXISTS expression), we may not need to require all indices.
343            plan.inputs()
344                .into_iter()
345                .map(RequiredIndices::new_for_all_exprs)
346                .collect()
347        }
348        LogicalPlan::Extension(extension) => {
349            if let Some(necessary_children_indices) =
350                extension.node.necessary_children_exprs(indices.indices())
351            {
352                let children = extension.node.inputs();
353                assert_eq_or_internal_err!(
354                    children.len(),
355                    necessary_children_indices.len(),
356                    "Inconsistent length between children and necessary children indices. \
357                Make sure `.necessary_children_exprs` implementation of the \
358                `UserDefinedLogicalNode` is consistent with actual children length \
359                for the node."
360                );
361                children
362                    .into_iter()
363                    .zip(necessary_children_indices)
364                    .map(|(child, necessary_indices)| {
365                        RequiredIndices::new_from_indices(necessary_indices)
366                            .with_plan_exprs(&plan, child.schema())
367                    })
368                    .collect::<Result<Vec<_>>>()?
369            } else {
370                // Requirements from parent cannot be routed down to user defined logical plan safely
371                // Assume it requires all input exprs here
372                plan.inputs()
373                    .into_iter()
374                    .map(RequiredIndices::new_for_all_exprs)
375                    .collect()
376            }
377        }
378        LogicalPlan::EmptyRelation(_)
379        | LogicalPlan::Values(_)
380        | LogicalPlan::DescribeTable(_) => {
381            // These operators have no inputs, so stop the optimization process.
382            return Ok(Transformed::no(plan));
383        }
384        LogicalPlan::RecursiveQuery(recursive) => {
385            // Only allow subqueries that reference the current CTE; nested subqueries are not yet
386            // supported for projection pushdown for simplicity.
387            // TODO: be able to do projection pushdown on recursive CTEs with subqueries
388            if plan_contains_other_subqueries(
389                recursive.static_term.as_ref(),
390                &recursive.name,
391            ) || plan_contains_other_subqueries(
392                recursive.recursive_term.as_ref(),
393                &recursive.name,
394            ) {
395                return Ok(Transformed::no(plan));
396            }
397
398            plan.inputs()
399                .into_iter()
400                .map(|input| {
401                    indices
402                        .clone()
403                        .with_projection_beneficial()
404                        .with_plan_exprs(&plan, input.schema())
405                })
406                .collect::<Result<Vec<_>>>()?
407        }
408        LogicalPlan::Join(join) => {
409            let left_len = join.left.schema().fields().len();
410            let right_len = join.right.schema().fields().len();
411            let (left_req_indices, right_req_indices) =
412                split_join_requirements(left_len, right_len, indices, &join.join_type);
413            let mut left_indices =
414                left_req_indices.with_plan_exprs(&plan, join.left.schema())?;
415            let mut right_indices =
416                right_req_indices.with_plan_exprs(&plan, join.right.schema())?;
417            // Ensure an empty mark join still has a column to qualify mark
418            match join.join_type {
419                JoinType::LeftMark if right_indices.indices().is_empty() => {
420                    right_indices = right_indices.append(&[0]);
421                }
422                JoinType::RightMark if left_indices.indices().is_empty() => {
423                    left_indices = left_indices.append(&[0]);
424                }
425                _ => {}
426            }
427            // Joins benefit from "small" input tables (lower memory usage).
428            // Therefore, each child benefits from projection:
429            vec![
430                left_indices.with_projection_beneficial(),
431                right_indices.with_projection_beneficial(),
432            ]
433        }
434        // these nodes are explicitly rewritten in the match statement above
435        LogicalPlan::Projection(_)
436        | LogicalPlan::Aggregate(_)
437        | LogicalPlan::Window(_)
438        | LogicalPlan::TableScan(_) => {
439            return internal_err!(
440                "OptimizeProjection: should have handled in the match statement above"
441            );
442        }
443        LogicalPlan::Unnest(Unnest {
444            input,
445            dependency_indices,
446            ..
447        }) => {
448            // at least provide the indices for the exec-columns as a starting point
449            let required_indices =
450                RequiredIndices::new().with_plan_exprs(&plan, input.schema())?;
451
452            // Add additional required indices from the parent
453            let mut additional_necessary_child_indices = Vec::new();
454            indices.indices().iter().for_each(|idx| {
455                if let Some(index) = dependency_indices.get(*idx) {
456                    additional_necessary_child_indices.push(*index);
457                }
458            });
459            vec![required_indices.append(&additional_necessary_child_indices)]
460        }
461    };
462
463    // Required indices are currently ordered (child0, child1, ...)
464    // but the loop pops off the last element, so we need to reverse the order
465    child_required_indices.reverse();
466    assert_eq_or_internal_err!(
467        child_required_indices.len(),
468        plan.inputs().len(),
469        "OptimizeProjection: child_required_indices length mismatch with plan inputs"
470    );
471
472    // Rewrite children of the plan
473    let transformed_plan = plan.map_children(|child| {
474        let required_indices = child_required_indices.pop().ok_or_else(|| {
475            internal_datafusion_err!(
476                "Unexpected number of required_indices in OptimizeProjections rule"
477            )
478        })?;
479
480        let projection_beneficial = required_indices.projection_beneficial();
481        let project_exprs = required_indices.get_required_exprs(child.schema());
482
483        optimize_projections(child, config, required_indices)?.transform_data(
484            |new_input| {
485                if projection_beneficial {
486                    add_projection_on_top_if_helpful(new_input, project_exprs)
487                } else {
488                    Ok(Transformed::no(new_input))
489                }
490            },
491        )
492    })?;
493
494    let transformed_plan =
495        transformed_plan.transform_data(|plan| optimize_subqueries(plan, config))?;
496
497    // If any of the children are transformed, we need to potentially update the plan's schema
498    if transformed_plan.transformed {
499        transformed_plan.map_data(|plan| plan.recompute_schema())
500    } else {
501        Ok(transformed_plan)
502    }
503}
504
505/// Optimizes uncorrelated subquery plans embedded in expressions of the given
506/// plan node (e.g., `Expr::ScalarSubquery`). `map_children` only visits direct
507/// plan inputs, so subqueries must be handled separately.
508fn optimize_subqueries(
509    plan: LogicalPlan,
510    config: &dyn OptimizerConfig,
511) -> Result<Transformed<LogicalPlan>> {
512    plan.map_uncorrelated_subqueries(|subquery_plan| {
513        let indices = RequiredIndices::new_for_all_exprs(&subquery_plan);
514        optimize_projections(subquery_plan, config, indices)
515    })
516}
517
518/// Given a projection `proj`, this function attempts to merge it with a previous
519/// projection if it exists and if merging is beneficial. Merging is considered
520/// beneficial when expressions in the current projection are non-trivial and
521/// appear more than once in its input fields. This can act as a caching mechanism
522/// for non-trivial computations.
523///
524/// ## Metadata Handling During Projection Merging
525///
526/// **Alias metadata preservation**: When merging projections, alias metadata from both
527/// the current and previous projections is carefully preserved. The presence of metadata
528/// precludes alias trimming.
529///
530/// **Schema, Fields, and metadata**: If a projection is rewritten, the schema and metadata
531/// are preserved. Individual field properties and metadata flows through expression rewriting
532/// and are preserved when fields are referenced in the merged projection.
533/// Refer to [`projection_schema`](datafusion_expr::logical_plan::projection_schema)
534/// for more details.
535///
536/// # Parameters
537///
538/// * `proj` - A reference to the `Projection` to be merged.
539///
540/// # Returns
541///
542/// A `Result` object with the following semantics:
543///
544/// - `Ok(Some(Projection))`: Merge was beneficial and successful. Contains the
545///   merged projection.
546/// - `Ok(None)`: Signals that merge is not beneficial (and has not taken place).
547/// - `Err(error)`: An error occurred during the function call.
548fn merge_consecutive_projections(proj: Projection) -> Result<Transformed<Projection>> {
549    let Projection {
550        expr,
551        input,
552        schema,
553        ..
554    } = proj;
555    let LogicalPlan::Projection(prev_projection) = input.as_ref() else {
556        return Projection::try_new_with_schema(expr, input, schema).map(Transformed::no);
557    };
558
559    // A fast path: if the previous projection is same as the current projection
560    // we can directly remove the current projection and return child projection.
561    if prev_projection.expr == expr {
562        return Projection::try_new_with_schema(
563            expr,
564            Arc::clone(&prev_projection.input),
565            schema,
566        )
567        .map(Transformed::yes);
568    }
569
570    // Count usages (referrals) of each projection expression in its input fields:
571    let mut column_referral_map = HashMap::<&Column, usize>::new();
572    expr.iter()
573        .for_each(|expr| expr.add_column_ref_counts(&mut column_referral_map));
574
575    // If an expression is non-trivial (KeepInPlace) and appears more than once, do not merge
576    // them as consecutive projections will benefit from a compute-once approach.
577    // For details, see: https://github.com/apache/datafusion/issues/8296
578    if column_referral_map.into_iter().any(|(col, usage)| {
579        usage > 1
580            && !prev_projection.expr[prev_projection.schema.index_of_column(col).unwrap()]
581                .placement()
582                .should_push_to_leaves()
583    }) {
584        // no change
585        return Projection::try_new_with_schema(expr, input, schema).map(Transformed::no);
586    }
587
588    let LogicalPlan::Projection(prev_projection) = Arc::unwrap_or_clone(input) else {
589        // We know it is a `LogicalPlan::Projection` from check above
590        unreachable!();
591    };
592
593    // Try to rewrite the expressions in the current projection using the
594    // previous projection as input:
595    let name_preserver = NamePreserver::new_for_projection();
596    let mut original_names = vec![];
597    let new_exprs = expr.map_elements(|expr| {
598        original_names.push(name_preserver.save(&expr));
599
600        // do not rewrite top level Aliases (rewriter will remove all aliases within exprs)
601        match expr {
602            Expr::Alias(Alias {
603                expr,
604                relation,
605                name,
606                metadata,
607            }) => rewrite_expr(*expr, &prev_projection).map(|result| {
608                result.update_data(|expr| {
609                    // After substitution, the inner expression may now have the
610                    // same schema_name as the alias (e.g. when an extraction
611                    // alias like `__extracted_1 AS f(x)` is resolved back to
612                    // `f(x)`). Wrapping in a redundant self-alias causes a
613                    // cosmetic `f(x) AS f(x)` due to Display vs schema_name
614                    // formatting differences. Drop the alias when it matches.
615                    if metadata.is_none() && expr.schema_name().to_string() == name {
616                        expr
617                    } else {
618                        Expr::Alias(Alias {
619                            expr: Box::new(expr),
620                            relation,
621                            name,
622                            metadata,
623                        })
624                    }
625                })
626            }),
627            e => rewrite_expr(e, &prev_projection),
628        }
629    })?;
630
631    // if the expressions could be rewritten, create a new projection with the
632    // new expressions
633    if new_exprs.transformed {
634        // Add any needed aliases back to the expressions
635        let new_exprs = new_exprs
636            .data
637            .into_iter()
638            .zip(original_names)
639            .map(|(expr, original_name)| original_name.restore(expr))
640            .collect::<Vec<_>>();
641        Projection::try_new(new_exprs, prev_projection.input).map(Transformed::yes)
642    } else {
643        // not rewritten, so put the projection back together
644        let input = Arc::new(LogicalPlan::Projection(prev_projection));
645        Projection::try_new_with_schema(new_exprs.data, input, schema)
646            .map(Transformed::no)
647    }
648}
649
650/// Rewrites a projection expression using the projection before it (i.e. its input)
651/// This is a subroutine to the `merge_consecutive_projections` function.
652///
653/// # Parameters
654///
655/// * `expr` - A reference to the expression to rewrite.
656/// * `input` - A reference to the input of the projection expression (itself
657///   a projection).
658///
659/// # Returns
660///
661/// A `Result` object with the following semantics:
662///
663/// - `Ok(Some(Expr))`: Rewrite was successful. Contains the rewritten result.
664/// - `Ok(None)`: Signals that `expr` can not be rewritten.
665/// - `Err(error)`: An error occurred during the function call.
666///
667/// # Notes
668/// This rewrite also removes any unnecessary layers of aliasing. "Unnecessary" is
669/// defined as not contributing new information, such as metadata.
670///
671/// Without trimming, we can end up with unnecessary indirections inside expressions
672/// during projection merges.
673///
674/// Consider:
675///
676/// ```text
677/// Projection(a1 + b1 as sum1)
678/// --Projection(a as a1, b as b1)
679/// ----Source(a, b)
680/// ```
681///
682/// After merge, we want to produce:
683///
684/// ```text
685/// Projection(a + b as sum1)
686/// --Source(a, b)
687/// ```
688///
689/// Without trimming, we would end up with:
690///
691/// ```text
692/// Projection((a as a1 + b as b1) as sum1)
693/// --Source(a, b)
694/// ```
695fn rewrite_expr(expr: Expr, input: &Projection) -> Result<Transformed<Expr>> {
696    expr.transform_up(|expr| {
697        match expr {
698            //  remove any intermediate aliases if they do not carry metadata
699            Expr::Alias(alias) => {
700                match alias
701                    .metadata
702                    .as_ref()
703                    .map(|h| h.is_empty())
704                    .unwrap_or(true)
705                {
706                    true => Ok(Transformed::yes(*alias.expr)),
707                    false => Ok(Transformed::no(Expr::Alias(alias))),
708                }
709            }
710            Expr::Column(col) => {
711                // Find index of column:
712                let idx = input.schema.index_of_column(&col)?;
713                // get the corresponding unaliased input expression
714                //
715                // For example:
716                // * the input projection is [`a + b` as c, `d + e` as f]
717                // * the current column is an expression "f"
718                //
719                // return the expression `d + e` (not `d + e` as f)
720                let input_expr = input.expr[idx].clone().unalias_nested().data;
721                Ok(Transformed::yes(input_expr))
722            }
723            // Unsupported type for consecutive projection merge analysis.
724            _ => Ok(Transformed::no(expr)),
725        }
726    })
727}
728
729/// Splits requirement indices for a join into left and right children based on
730/// the join type.
731///
732/// This function takes the length of the left child, a slice of requirement
733/// indices, and the type of join (e.g. `INNER`, `LEFT`, `RIGHT`) as arguments.
734/// Depending on the join type, it divides the requirement indices into those
735/// that apply to the left child and those that apply to the right child.
736///
737/// - For `INNER`, `LEFT`, `RIGHT`, `FULL`, `LEFTMARK`, and `RIGHTMARK` joins,
738///   the requirements are split between left and right children. The right
739///   child indices are adjusted to point to valid positions within the right
740///   child by subtracting the length of the left child.
741///
742/// - For `LEFT ANTI`, `LEFT SEMI`, `RIGHT SEMI` and `RIGHT ANTI` joins, all
743///   requirements are re-routed to either the left child or the right child
744///   directly, depending on the join type.
745///
746/// # Parameters
747///
748/// * `left_len` - The length of the left child.
749/// * `right_len` - The length of the right child.
750/// * `indices` - A slice of requirement indices.
751/// * `join_type` - The type of join (e.g. `INNER`, `LEFT`, `RIGHT`).
752///
753/// # Returns
754///
755/// A tuple containing two vectors of `usize` indices: The first vector represents
756/// the requirements for the left child, and the second vector represents the
757/// requirements for the right child. The indices are appropriately split and
758/// adjusted based on the join type.
759fn split_join_requirements(
760    left_len: usize,
761    right_len: usize,
762    indices: RequiredIndices,
763    join_type: &JoinType,
764) -> (RequiredIndices, RequiredIndices) {
765    match join_type {
766        // In these cases requirements are split between left/right children:
767        JoinType::Inner | JoinType::Left | JoinType::Right | JoinType::Full => {
768            // Decrease right side indices by `left_len` so that they point to valid
769            // positions within the right child:
770            indices.split_off(left_len)
771        }
772        JoinType::LeftMark => {
773            // LeftMark output: [left_cols(0..left_len), mark]
774            // The mark column is synthetic (produced by the join itself),
775            // so discard it and route only to the left child.
776            let (left_indices, _mark) = indices.split_off(left_len);
777            (left_indices, RequiredIndices::new())
778        }
779        JoinType::RightMark => {
780            // Same as LeftMark, but for the right child.
781            let (right_indices, _mark) = indices.split_off(right_len);
782            (RequiredIndices::new(), right_indices)
783        }
784        // All requirements can be re-routed to left child directly.
785        JoinType::LeftAnti | JoinType::LeftSemi => (indices, RequiredIndices::new()),
786        // All requirements can be re-routed to right side directly.
787        // No need to change index, join schema is right child schema.
788        JoinType::RightSemi | JoinType::RightAnti => (RequiredIndices::new(), indices),
789    }
790}
791
792/// Adds a projection on top of a logical plan if doing so reduces the number
793/// of columns for the parent operator.
794///
795/// This function takes a `LogicalPlan` and a list of projection expressions.
796/// If the projection is beneficial (it reduces the number of columns in the
797/// plan) a new `LogicalPlan` with the projection is created and returned, along
798/// with a `true` flag. If the projection doesn't reduce the number of columns,
799/// the original plan is returned with a `false` flag.
800///
801/// # Parameters
802///
803/// * `plan` - The input `LogicalPlan` to potentially add a projection to.
804/// * `project_exprs` - A list of expressions for the projection.
805///
806/// # Returns
807///
808/// A `Transformed` indicating if a projection was added
809fn add_projection_on_top_if_helpful(
810    plan: LogicalPlan,
811    project_exprs: Vec<Expr>,
812) -> Result<Transformed<LogicalPlan>> {
813    // Make sure projection decreases the number of columns, otherwise it is unnecessary.
814    if project_exprs.len() >= plan.schema().fields().len() {
815        Ok(Transformed::no(plan))
816    } else {
817        Projection::try_new(project_exprs, Arc::new(plan))
818            .map(LogicalPlan::Projection)
819            .map(Transformed::yes)
820    }
821}
822
823/// Rewrite the given projection according to the fields required by its
824/// ancestors.
825///
826/// # Parameters
827///
828/// * `proj` - A reference to the original projection to rewrite.
829/// * `config` - A reference to the optimizer configuration.
830/// * `indices` - A slice of indices representing the columns required by the
831///   ancestors of the given projection.
832///
833/// # Returns
834///
835/// A `Result` object with the following semantics:
836///
837/// - `Ok(Some(LogicalPlan))`: Contains the rewritten projection
838/// - `Ok(None)`: No rewrite necessary.
839/// - `Err(error)`: An error occurred during the function call.
840fn rewrite_projection_given_requirements(
841    proj: Projection,
842    config: &dyn OptimizerConfig,
843    indices: &RequiredIndices,
844) -> Result<Transformed<LogicalPlan>> {
845    let Projection { expr, input, .. } = proj;
846
847    let exprs_used = indices.get_at_indices(&expr);
848
849    let required_indices =
850        RequiredIndices::new().with_exprs(input.schema(), exprs_used.iter());
851
852    // rewrite the children projection, and if they are changed rewrite the
853    // projection down
854    optimize_projections(Arc::unwrap_or_clone(input), config, required_indices)?
855        .transform_data(|input| {
856            if is_projection_unnecessary(&input, &exprs_used)? {
857                Ok(Transformed::yes(input))
858            } else {
859                Projection::try_new(exprs_used, Arc::new(input))
860                    .map(LogicalPlan::Projection)
861                    .map(Transformed::yes)
862            }
863        })
864}
865
866/// Projection is unnecessary, when
867/// - input schema of the projection, output schema of the projection are same, and
868/// - all projection expressions are either Column or Literal
869pub fn is_projection_unnecessary(
870    input: &LogicalPlan,
871    proj_exprs: &[Expr],
872) -> Result<bool> {
873    // First check if the number of expressions is equal to the number of fields in the input schema.
874    if proj_exprs.len() != input.schema().fields().len() {
875        return Ok(false);
876    }
877    Ok(input.schema().iter().zip(proj_exprs.iter()).all(
878        |((field_relation, field_name), expr)| {
879            // Check if the expression is a column and if it matches the field name
880            if let Expr::Column(col) = expr {
881                col.relation.as_ref() == field_relation && col.name.eq(field_name.name())
882            } else {
883                false
884            }
885        },
886    ))
887}
888
889/// Returns true if the plan subtree contains any subqueries that are not the
890/// CTE reference itself. This treats any non-CTE [`LogicalPlan::SubqueryAlias`]
891/// node (including aliased relations) as a blocker, along with expression-level
892/// subqueries like scalar, EXISTS, or IN. These cases prevent projection
893/// pushdown for now because we cannot safely reason about their column usage.
894fn plan_contains_other_subqueries(plan: &LogicalPlan, cte_name: &str) -> bool {
895    if let LogicalPlan::SubqueryAlias(alias) = plan
896        && alias.alias.table() != cte_name
897        && !subquery_alias_targets_recursive_cte(alias.input.as_ref(), cte_name)
898    {
899        return true;
900    }
901
902    let mut found = false;
903    plan.apply_expressions(|expr| {
904        if expr_contains_subquery(expr) {
905            found = true;
906            Ok(TreeNodeRecursion::Stop)
907        } else {
908            Ok(TreeNodeRecursion::Continue)
909        }
910    })
911    .expect("expression traversal never fails");
912    if found {
913        return true;
914    }
915
916    plan.inputs()
917        .into_iter()
918        .any(|child| plan_contains_other_subqueries(child, cte_name))
919}
920
921fn expr_contains_subquery(expr: &Expr) -> bool {
922    expr.exists(|e| match e {
923        Expr::ScalarSubquery(_) | Expr::Exists(_) | Expr::InSubquery(_) => Ok(true),
924        _ => Ok(false),
925    })
926    // Safe unwrap since we are doing a simple boolean check
927    .unwrap()
928}
929
930fn subquery_alias_targets_recursive_cte(plan: &LogicalPlan, cte_name: &str) -> bool {
931    match plan {
932        LogicalPlan::TableScan(scan) => scan.table_name.table() == cte_name,
933        LogicalPlan::SubqueryAlias(alias) => {
934            subquery_alias_targets_recursive_cte(alias.input.as_ref(), cte_name)
935        }
936        _ => {
937            let inputs = plan.inputs();
938            if inputs.len() == 1 {
939                subquery_alias_targets_recursive_cte(inputs[0], cte_name)
940            } else {
941                false
942            }
943        }
944    }
945}
946
947#[cfg(test)]
948mod tests {
949    use std::cmp::Ordering;
950    use std::collections::HashMap;
951    use std::fmt::Formatter;
952    use std::ops::Add;
953    use std::sync::Arc;
954    use std::vec;
955
956    use crate::optimize_projections::OptimizeProjections;
957    use crate::optimizer::Optimizer;
958    use crate::test::{
959        assert_fields_eq, scan_empty, test_table_scan, test_table_scan_fields,
960        test_table_scan_with_name,
961    };
962    use crate::{OptimizerContext, OptimizerRule};
963    use arrow::datatypes::{DataType, Field, Schema};
964    use datafusion_common::{
965        Column, DFSchema, DFSchemaRef, JoinType, Result, TableReference,
966    };
967    use datafusion_expr::ExprFunctionExt;
968    use datafusion_expr::{
969        BinaryExpr, Expr, Extension, Like, LogicalPlan, Operator, Projection,
970        UserDefinedLogicalNodeCore, WindowFunctionDefinition, binary_expr,
971        build_join_schema,
972        builder::table_scan_with_filters,
973        col,
974        expr::{self, Cast},
975        lit,
976        logical_plan::{builder::LogicalPlanBuilder, table_scan},
977        not, try_cast, when,
978    };
979    use insta::assert_snapshot;
980
981    use crate::assert_optimized_plan_eq_snapshot;
982    use datafusion_functions_aggregate::count::count_udaf;
983    use datafusion_functions_aggregate::expr_fn::{count, max, min};
984    use datafusion_functions_aggregate::min_max::max_udaf;
985
986    macro_rules! assert_optimized_plan_equal {
987        (
988            $plan:expr,
989            @ $expected:literal $(,)?
990        ) => {{
991            let optimizer_ctx = OptimizerContext::new().with_max_passes(1);
992            let rules: Vec<Arc<dyn crate::OptimizerRule + Send + Sync>> = vec![Arc::new(OptimizeProjections::new())];
993            assert_optimized_plan_eq_snapshot!(
994                optimizer_ctx,
995                rules,
996                $plan,
997                @ $expected,
998            )
999        }};
1000    }
1001
1002    #[derive(Debug, Hash, PartialEq, Eq)]
1003    struct NoOpUserDefined {
1004        exprs: Vec<Expr>,
1005        schema: DFSchemaRef,
1006        input: Arc<LogicalPlan>,
1007    }
1008
1009    impl NoOpUserDefined {
1010        fn new(schema: DFSchemaRef, input: Arc<LogicalPlan>) -> Self {
1011            Self {
1012                exprs: vec![],
1013                schema,
1014                input,
1015            }
1016        }
1017
1018        fn with_exprs(mut self, exprs: Vec<Expr>) -> Self {
1019            self.exprs = exprs;
1020            self
1021        }
1022    }
1023
1024    // Manual implementation needed because of `schema` field. Comparison excludes this field.
1025    impl PartialOrd for NoOpUserDefined {
1026        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1027            match self.exprs.partial_cmp(&other.exprs) {
1028                Some(Ordering::Equal) => self.input.partial_cmp(&other.input),
1029                cmp => cmp,
1030            }
1031            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
1032            .filter(|cmp| *cmp != Ordering::Equal || self == other)
1033        }
1034    }
1035
1036    impl UserDefinedLogicalNodeCore for NoOpUserDefined {
1037        fn name(&self) -> &str {
1038            "NoOpUserDefined"
1039        }
1040
1041        fn inputs(&self) -> Vec<&LogicalPlan> {
1042            vec![&self.input]
1043        }
1044
1045        fn schema(&self) -> &DFSchemaRef {
1046            &self.schema
1047        }
1048
1049        fn expressions(&self) -> Vec<Expr> {
1050            self.exprs.clone()
1051        }
1052
1053        fn fmt_for_explain(&self, f: &mut Formatter) -> std::fmt::Result {
1054            write!(f, "NoOpUserDefined")
1055        }
1056
1057        fn with_exprs_and_inputs(
1058            &self,
1059            exprs: Vec<Expr>,
1060            mut inputs: Vec<LogicalPlan>,
1061        ) -> Result<Self> {
1062            Ok(Self {
1063                exprs,
1064                input: Arc::new(inputs.swap_remove(0)),
1065                schema: Arc::clone(&self.schema),
1066            })
1067        }
1068
1069        fn necessary_children_exprs(
1070            &self,
1071            output_columns: &[usize],
1072        ) -> Option<Vec<Vec<usize>>> {
1073            // Since schema is same. Output columns requires their corresponding version in the input columns.
1074            Some(vec![output_columns.to_vec()])
1075        }
1076
1077        fn supports_limit_pushdown(&self) -> bool {
1078            false // Disallow limit push-down by default
1079        }
1080    }
1081
1082    #[derive(Debug, Hash, PartialEq, Eq)]
1083    struct UserDefinedCrossJoin {
1084        exprs: Vec<Expr>,
1085        schema: DFSchemaRef,
1086        left_child: Arc<LogicalPlan>,
1087        right_child: Arc<LogicalPlan>,
1088    }
1089
1090    impl UserDefinedCrossJoin {
1091        fn new(left_child: Arc<LogicalPlan>, right_child: Arc<LogicalPlan>) -> Self {
1092            let left_schema = left_child.schema();
1093            let right_schema = right_child.schema();
1094            let schema = Arc::new(
1095                build_join_schema(left_schema, right_schema, &JoinType::Inner).unwrap(),
1096            );
1097            Self {
1098                exprs: vec![],
1099                schema,
1100                left_child,
1101                right_child,
1102            }
1103        }
1104    }
1105
1106    // Manual implementation needed because of `schema` field. Comparison excludes this field.
1107    impl PartialOrd for UserDefinedCrossJoin {
1108        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1109            match self.exprs.partial_cmp(&other.exprs) {
1110                Some(Ordering::Equal) => {
1111                    match self.left_child.partial_cmp(&other.left_child) {
1112                        Some(Ordering::Equal) => {
1113                            self.right_child.partial_cmp(&other.right_child)
1114                        }
1115                        cmp => cmp,
1116                    }
1117                }
1118                cmp => cmp,
1119            }
1120            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
1121            .filter(|cmp| *cmp != Ordering::Equal || self == other)
1122        }
1123    }
1124
1125    impl UserDefinedLogicalNodeCore for UserDefinedCrossJoin {
1126        fn name(&self) -> &str {
1127            "UserDefinedCrossJoin"
1128        }
1129
1130        fn inputs(&self) -> Vec<&LogicalPlan> {
1131            vec![&self.left_child, &self.right_child]
1132        }
1133
1134        fn schema(&self) -> &DFSchemaRef {
1135            &self.schema
1136        }
1137
1138        fn expressions(&self) -> Vec<Expr> {
1139            self.exprs.clone()
1140        }
1141
1142        fn fmt_for_explain(&self, f: &mut Formatter) -> std::fmt::Result {
1143            write!(f, "UserDefinedCrossJoin")
1144        }
1145
1146        fn with_exprs_and_inputs(
1147            &self,
1148            exprs: Vec<Expr>,
1149            mut inputs: Vec<LogicalPlan>,
1150        ) -> Result<Self> {
1151            assert_eq!(inputs.len(), 2);
1152            Ok(Self {
1153                exprs,
1154                left_child: Arc::new(inputs.remove(0)),
1155                right_child: Arc::new(inputs.remove(0)),
1156                schema: Arc::clone(&self.schema),
1157            })
1158        }
1159
1160        fn necessary_children_exprs(
1161            &self,
1162            output_columns: &[usize],
1163        ) -> Option<Vec<Vec<usize>>> {
1164            let left_child_len = self.left_child.schema().fields().len();
1165            let mut left_reqs = vec![];
1166            let mut right_reqs = vec![];
1167            for &out_idx in output_columns {
1168                if out_idx < left_child_len {
1169                    left_reqs.push(out_idx);
1170                } else {
1171                    // Output indices further than the left_child_len
1172                    // comes from right children
1173                    right_reqs.push(out_idx - left_child_len)
1174                }
1175            }
1176            Some(vec![left_reqs, right_reqs])
1177        }
1178
1179        fn supports_limit_pushdown(&self) -> bool {
1180            false // Disallow limit push-down by default
1181        }
1182    }
1183
1184    /// A user-defined node that does NOT implement `necessary_children_exprs`,
1185    /// so the optimizer cannot determine which columns are required from its
1186    /// children and must assume all columns are needed.
1187    #[derive(Debug, Hash, PartialEq, Eq)]
1188    struct OpaqueRequirementsUserDefined {
1189        input: Arc<LogicalPlan>,
1190        schema: DFSchemaRef,
1191    }
1192
1193    // Manual implementation needed because of `schema` field. Comparison excludes this field.
1194    impl PartialOrd for OpaqueRequirementsUserDefined {
1195        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1196            self.input
1197                .partial_cmp(&other.input)
1198                .filter(|cmp| *cmp != Ordering::Equal || self == other)
1199        }
1200    }
1201
1202    impl UserDefinedLogicalNodeCore for OpaqueRequirementsUserDefined {
1203        fn name(&self) -> &str {
1204            "OpaqueRequirementsUserDefined"
1205        }
1206
1207        fn inputs(&self) -> Vec<&LogicalPlan> {
1208            vec![&self.input]
1209        }
1210
1211        fn schema(&self) -> &DFSchemaRef {
1212            &self.schema
1213        }
1214
1215        fn expressions(&self) -> Vec<Expr> {
1216            vec![]
1217        }
1218
1219        fn with_exprs_and_inputs(
1220            &self,
1221            _exprs: Vec<Expr>,
1222            mut inputs: Vec<LogicalPlan>,
1223        ) -> Result<Self> {
1224            Ok(Self {
1225                input: Arc::new(inputs.swap_remove(0)),
1226                schema: Arc::clone(&self.schema),
1227            })
1228        }
1229
1230        fn fmt_for_explain(&self, f: &mut Formatter) -> std::fmt::Result {
1231            write!(f, "OpaqueRequirementsUserDefined")
1232        }
1233    }
1234
1235    #[test]
1236    fn merge_two_projection() -> Result<()> {
1237        let table_scan = test_table_scan()?;
1238        let plan = LogicalPlanBuilder::from(table_scan)
1239            .project(vec![col("a")])?
1240            .project(vec![binary_expr(lit(1), Operator::Plus, col("a"))])?
1241            .build()?;
1242
1243        assert_optimized_plan_equal!(
1244            plan,
1245            @r"
1246        Projection: Int32(1) + test.a
1247          TableScan: test projection=[a]
1248        "
1249        )
1250    }
1251
1252    #[test]
1253    fn merge_three_projection() -> Result<()> {
1254        let table_scan = test_table_scan()?;
1255        let plan = LogicalPlanBuilder::from(table_scan)
1256            .project(vec![col("a"), col("b")])?
1257            .project(vec![col("a")])?
1258            .project(vec![binary_expr(lit(1), Operator::Plus, col("a"))])?
1259            .build()?;
1260
1261        assert_optimized_plan_equal!(
1262            plan,
1263            @r"
1264        Projection: Int32(1) + test.a
1265          TableScan: test projection=[a]
1266        "
1267        )
1268    }
1269
1270    #[test]
1271    fn merge_alias() -> Result<()> {
1272        let table_scan = test_table_scan()?;
1273        let plan = LogicalPlanBuilder::from(table_scan)
1274            .project(vec![col("a")])?
1275            .project(vec![col("a").alias("alias")])?
1276            .build()?;
1277
1278        assert_optimized_plan_equal!(
1279            plan,
1280            @r"
1281        Projection: test.a AS alias
1282          TableScan: test projection=[a]
1283        "
1284        )
1285    }
1286
1287    #[test]
1288    fn merge_nested_alias() -> Result<()> {
1289        let table_scan = test_table_scan()?;
1290        let plan = LogicalPlanBuilder::from(table_scan)
1291            .project(vec![col("a").alias("alias1").alias("alias2")])?
1292            .project(vec![col("alias2").alias("alias")])?
1293            .build()?;
1294
1295        assert_optimized_plan_equal!(
1296            plan,
1297            @r"
1298        Projection: test.a AS alias
1299          TableScan: test projection=[a]
1300        "
1301        )
1302    }
1303
1304    #[test]
1305    fn test_nested_count() -> Result<()> {
1306        let schema = Schema::new(vec![Field::new("foo", DataType::Int32, false)]);
1307
1308        let groups: Vec<Expr> = vec![];
1309
1310        let plan = table_scan(TableReference::none(), &schema, None)
1311            .unwrap()
1312            .aggregate(groups.clone(), vec![count(lit(1))])
1313            .unwrap()
1314            .aggregate(groups, vec![count(lit(1))])
1315            .unwrap()
1316            .build()
1317            .unwrap();
1318
1319        assert_optimized_plan_equal!(
1320            plan,
1321            @r"
1322        Aggregate: groupBy=[[]], aggr=[[count(Int32(1))]]
1323          EmptyRelation: rows=1
1324        "
1325        )
1326    }
1327
1328    #[test]
1329    fn test_neg_push_down() -> Result<()> {
1330        let table_scan = test_table_scan()?;
1331        let plan = LogicalPlanBuilder::from(table_scan)
1332            .project(vec![-col("a")])?
1333            .build()?;
1334
1335        assert_optimized_plan_equal!(
1336            plan,
1337            @r"
1338        Projection: (- test.a)
1339          TableScan: test projection=[a]
1340        "
1341        )
1342    }
1343
1344    #[test]
1345    fn test_is_null() -> Result<()> {
1346        let table_scan = test_table_scan()?;
1347        let plan = LogicalPlanBuilder::from(table_scan)
1348            .project(vec![col("a").is_null()])?
1349            .build()?;
1350
1351        assert_optimized_plan_equal!(
1352            plan,
1353            @r"
1354        Projection: test.a IS NULL
1355          TableScan: test projection=[a]
1356        "
1357        )
1358    }
1359
1360    #[test]
1361    fn test_is_not_null() -> Result<()> {
1362        let table_scan = test_table_scan()?;
1363        let plan = LogicalPlanBuilder::from(table_scan)
1364            .project(vec![col("a").is_not_null()])?
1365            .build()?;
1366
1367        assert_optimized_plan_equal!(
1368            plan,
1369            @r"
1370        Projection: test.a IS NOT NULL
1371          TableScan: test projection=[a]
1372        "
1373        )
1374    }
1375
1376    #[test]
1377    fn test_is_true() -> Result<()> {
1378        let table_scan = test_table_scan()?;
1379        let plan = LogicalPlanBuilder::from(table_scan)
1380            .project(vec![col("a").is_true()])?
1381            .build()?;
1382
1383        assert_optimized_plan_equal!(
1384            plan,
1385            @r"
1386        Projection: test.a IS TRUE
1387          TableScan: test projection=[a]
1388        "
1389        )
1390    }
1391
1392    #[test]
1393    fn test_is_not_true() -> Result<()> {
1394        let table_scan = test_table_scan()?;
1395        let plan = LogicalPlanBuilder::from(table_scan)
1396            .project(vec![col("a").is_not_true()])?
1397            .build()?;
1398
1399        assert_optimized_plan_equal!(
1400            plan,
1401            @r"
1402        Projection: test.a IS NOT TRUE
1403          TableScan: test projection=[a]
1404        "
1405        )
1406    }
1407
1408    #[test]
1409    fn test_is_false() -> Result<()> {
1410        let table_scan = test_table_scan()?;
1411        let plan = LogicalPlanBuilder::from(table_scan)
1412            .project(vec![col("a").is_false()])?
1413            .build()?;
1414
1415        assert_optimized_plan_equal!(
1416            plan,
1417            @r"
1418        Projection: test.a IS FALSE
1419          TableScan: test projection=[a]
1420        "
1421        )
1422    }
1423
1424    #[test]
1425    fn test_is_not_false() -> Result<()> {
1426        let table_scan = test_table_scan()?;
1427        let plan = LogicalPlanBuilder::from(table_scan)
1428            .project(vec![col("a").is_not_false()])?
1429            .build()?;
1430
1431        assert_optimized_plan_equal!(
1432            plan,
1433            @r"
1434        Projection: test.a IS NOT FALSE
1435          TableScan: test projection=[a]
1436        "
1437        )
1438    }
1439
1440    #[test]
1441    fn test_is_unknown() -> Result<()> {
1442        let table_scan = test_table_scan()?;
1443        let plan = LogicalPlanBuilder::from(table_scan)
1444            .project(vec![col("a").is_unknown()])?
1445            .build()?;
1446
1447        assert_optimized_plan_equal!(
1448            plan,
1449            @r"
1450        Projection: test.a IS UNKNOWN
1451          TableScan: test projection=[a]
1452        "
1453        )
1454    }
1455
1456    #[test]
1457    fn test_is_not_unknown() -> Result<()> {
1458        let table_scan = test_table_scan()?;
1459        let plan = LogicalPlanBuilder::from(table_scan)
1460            .project(vec![col("a").is_not_unknown()])?
1461            .build()?;
1462
1463        assert_optimized_plan_equal!(
1464            plan,
1465            @r"
1466        Projection: test.a IS NOT UNKNOWN
1467          TableScan: test projection=[a]
1468        "
1469        )
1470    }
1471
1472    #[test]
1473    fn test_not() -> Result<()> {
1474        let table_scan = test_table_scan()?;
1475        let plan = LogicalPlanBuilder::from(table_scan)
1476            .project(vec![not(col("a"))])?
1477            .build()?;
1478
1479        assert_optimized_plan_equal!(
1480            plan,
1481            @r"
1482        Projection: NOT test.a
1483          TableScan: test projection=[a]
1484        "
1485        )
1486    }
1487
1488    #[test]
1489    fn test_try_cast() -> Result<()> {
1490        let table_scan = test_table_scan()?;
1491        let plan = LogicalPlanBuilder::from(table_scan)
1492            .project(vec![try_cast(col("a"), DataType::Float64)])?
1493            .build()?;
1494
1495        assert_optimized_plan_equal!(
1496            plan,
1497            @r"
1498        Projection: TRY_CAST(test.a AS Float64)
1499          TableScan: test projection=[a]
1500        "
1501        )
1502    }
1503
1504    #[test]
1505    fn test_similar_to() -> Result<()> {
1506        let table_scan = test_table_scan()?;
1507        let expr = Box::new(col("a"));
1508        let pattern = Box::new(lit("[0-9]"));
1509        let similar_to_expr =
1510            Expr::SimilarTo(Like::new(false, expr, pattern, None, false));
1511        let plan = LogicalPlanBuilder::from(table_scan)
1512            .project(vec![similar_to_expr])?
1513            .build()?;
1514
1515        assert_optimized_plan_equal!(
1516            plan,
1517            @r#"
1518        Projection: test.a SIMILAR TO Utf8("[0-9]")
1519          TableScan: test projection=[a]
1520        "#
1521        )
1522    }
1523
1524    #[test]
1525    fn test_between() -> Result<()> {
1526        let table_scan = test_table_scan()?;
1527        let plan = LogicalPlanBuilder::from(table_scan)
1528            .project(vec![col("a").between(lit(1), lit(3))])?
1529            .build()?;
1530
1531        assert_optimized_plan_equal!(
1532            plan,
1533            @r"
1534        Projection: test.a BETWEEN Int32(1) AND Int32(3)
1535          TableScan: test projection=[a]
1536        "
1537        )
1538    }
1539
1540    // Test Case expression
1541    #[test]
1542    fn test_case_merged() -> Result<()> {
1543        let table_scan = test_table_scan()?;
1544        let plan = LogicalPlanBuilder::from(table_scan)
1545            .project(vec![col("a"), lit(0).alias("d")])?
1546            .project(vec![
1547                col("a"),
1548                when(col("a").eq(lit(1)), lit(10))
1549                    .otherwise(col("d"))?
1550                    .alias("d"),
1551            ])?
1552            .build()?;
1553
1554        assert_optimized_plan_equal!(
1555            plan,
1556            @r"
1557        Projection: test.a, CASE WHEN test.a = Int32(1) THEN Int32(10) ELSE Int32(0) END AS d
1558          TableScan: test projection=[a]
1559        "
1560        )
1561    }
1562
1563    // Test outer projection isn't discarded despite the same schema as inner
1564    // https://github.com/apache/datafusion/issues/8942
1565    #[test]
1566    fn test_derived_column() -> Result<()> {
1567        let table_scan = test_table_scan()?;
1568        let plan = LogicalPlanBuilder::from(table_scan)
1569            .project(vec![col("a").add(lit(1)).alias("a"), lit(0).alias("d")])?
1570            .project(vec![
1571                col("a"),
1572                when(col("a").eq(lit(1)), lit(10))
1573                    .otherwise(col("d"))?
1574                    .alias("d"),
1575            ])?
1576            .build()?;
1577
1578        assert_optimized_plan_equal!(
1579            plan,
1580            @r"
1581        Projection: a, CASE WHEN a = Int32(1) THEN Int32(10) ELSE d END AS d
1582          Projection: test.a + Int32(1) AS a, Int32(0) AS d
1583            TableScan: test projection=[a]
1584        "
1585        )
1586    }
1587
1588    // Since only column `a` is referred at the output. Scan should only contain projection=[a].
1589    // User defined node should be able to propagate necessary expressions by its parent to its child.
1590    #[test]
1591    fn test_user_defined_logical_plan_node() -> Result<()> {
1592        let table_scan = test_table_scan()?;
1593        let custom_plan = LogicalPlan::Extension(Extension {
1594            node: Arc::new(NoOpUserDefined::new(
1595                Arc::clone(table_scan.schema()),
1596                Arc::new(table_scan.clone()),
1597            )),
1598        });
1599        let plan = LogicalPlanBuilder::from(custom_plan)
1600            .project(vec![col("a"), lit(0).alias("d")])?
1601            .build()?;
1602
1603        assert_optimized_plan_equal!(
1604            plan,
1605            @r"
1606        Projection: test.a, Int32(0) AS d
1607          NoOpUserDefined
1608            TableScan: test projection=[a]
1609        "
1610        )
1611    }
1612
1613    // Only column `a` is referred at the output. However, User defined node itself uses column `b`
1614    // during its operation. Hence, scan should contain projection=[a, b].
1615    // User defined node should be able to propagate necessary expressions by its parent, as well as its own
1616    // required expressions.
1617    #[test]
1618    fn test_user_defined_logical_plan_node2() -> Result<()> {
1619        let table_scan = test_table_scan()?;
1620        let exprs = vec![Expr::Column(Column::from_qualified_name("b"))];
1621        let custom_plan = LogicalPlan::Extension(Extension {
1622            node: Arc::new(
1623                NoOpUserDefined::new(
1624                    Arc::clone(table_scan.schema()),
1625                    Arc::new(table_scan.clone()),
1626                )
1627                .with_exprs(exprs),
1628            ),
1629        });
1630        let plan = LogicalPlanBuilder::from(custom_plan)
1631            .project(vec![col("a"), lit(0).alias("d")])?
1632            .build()?;
1633
1634        assert_optimized_plan_equal!(
1635            plan,
1636            @r"
1637        Projection: test.a, Int32(0) AS d
1638          NoOpUserDefined
1639            TableScan: test projection=[a, b]
1640        "
1641        )
1642    }
1643
1644    // Only column `a` is referred at the output. However, User defined node itself uses expression `b+c`
1645    // during its operation. Hence, scan should contain projection=[a, b, c].
1646    // User defined node should be able to propagate necessary expressions by its parent, as well as its own
1647    // required expressions. Expressions doesn't have to be just column. Requirements from complex expressions
1648    // should be propagated also.
1649    #[test]
1650    fn test_user_defined_logical_plan_node3() -> Result<()> {
1651        let table_scan = test_table_scan()?;
1652        let left_expr = Expr::Column(Column::from_qualified_name("b"));
1653        let right_expr = Expr::Column(Column::from_qualified_name("c"));
1654        let binary_expr = Expr::BinaryExpr(BinaryExpr::new(
1655            Box::new(left_expr),
1656            Operator::Plus,
1657            Box::new(right_expr),
1658        ));
1659        let exprs = vec![binary_expr];
1660        let custom_plan = LogicalPlan::Extension(Extension {
1661            node: Arc::new(
1662                NoOpUserDefined::new(
1663                    Arc::clone(table_scan.schema()),
1664                    Arc::new(table_scan.clone()),
1665                )
1666                .with_exprs(exprs),
1667            ),
1668        });
1669        let plan = LogicalPlanBuilder::from(custom_plan)
1670            .project(vec![col("a"), lit(0).alias("d")])?
1671            .build()?;
1672
1673        assert_optimized_plan_equal!(
1674            plan,
1675            @r"
1676        Projection: test.a, Int32(0) AS d
1677          NoOpUserDefined
1678            TableScan: test projection=[a, b, c]
1679        "
1680        )
1681    }
1682
1683    // Columns `l.a`, `l.c`, `r.a` is referred at the output.
1684    // User defined node should be able to propagate necessary expressions by its parent, to its children.
1685    // Even if it has multiple children.
1686    // left child should have `projection=[a, c]`, and right side should have `projection=[a]`.
1687    #[test]
1688    fn test_user_defined_logical_plan_node4() -> Result<()> {
1689        let left_table = test_table_scan_with_name("l")?;
1690        let right_table = test_table_scan_with_name("r")?;
1691        let custom_plan = LogicalPlan::Extension(Extension {
1692            node: Arc::new(UserDefinedCrossJoin::new(
1693                Arc::new(left_table),
1694                Arc::new(right_table),
1695            )),
1696        });
1697        let plan = LogicalPlanBuilder::from(custom_plan)
1698            .project(vec![col("l.a"), col("l.c"), col("r.a"), lit(0).alias("d")])?
1699            .build()?;
1700
1701        assert_optimized_plan_equal!(
1702            plan,
1703            @r"
1704        Projection: l.a, l.c, r.a, Int32(0) AS d
1705          UserDefinedCrossJoin
1706            TableScan: l projection=[a, c]
1707            TableScan: r projection=[a]
1708        "
1709        )
1710    }
1711
1712    #[test]
1713    fn aggregate_no_group_by() -> Result<()> {
1714        let table_scan = test_table_scan()?;
1715
1716        let plan = LogicalPlanBuilder::from(table_scan)
1717            .aggregate(Vec::<Expr>::new(), vec![max(col("b"))])?
1718            .build()?;
1719
1720        assert_optimized_plan_equal!(
1721            plan,
1722            @r"
1723        Aggregate: groupBy=[[]], aggr=[[max(test.b)]]
1724          TableScan: test projection=[b]
1725        "
1726        )
1727    }
1728
1729    #[test]
1730    fn aggregate_group_by() -> Result<()> {
1731        let table_scan = test_table_scan()?;
1732
1733        let plan = LogicalPlanBuilder::from(table_scan)
1734            .aggregate(vec![col("c")], vec![max(col("b"))])?
1735            .build()?;
1736
1737        assert_optimized_plan_equal!(
1738            plan,
1739            @r"
1740        Aggregate: groupBy=[[test.c]], aggr=[[max(test.b)]]
1741          TableScan: test projection=[b, c]
1742        "
1743        )
1744    }
1745
1746    #[test]
1747    fn aggregate_group_by_with_table_alias() -> Result<()> {
1748        let table_scan = test_table_scan()?;
1749
1750        let plan = LogicalPlanBuilder::from(table_scan)
1751            .alias("a")?
1752            .aggregate(vec![col("c")], vec![max(col("b"))])?
1753            .build()?;
1754
1755        assert_optimized_plan_equal!(
1756            plan,
1757            @r"
1758        Aggregate: groupBy=[[a.c]], aggr=[[max(a.b)]]
1759          SubqueryAlias: a
1760            TableScan: test projection=[b, c]
1761        "
1762        )
1763    }
1764
1765    #[test]
1766    fn aggregate_no_group_by_with_filter() -> Result<()> {
1767        let table_scan = test_table_scan()?;
1768
1769        let plan = LogicalPlanBuilder::from(table_scan)
1770            .filter(col("c").gt(lit(1)))?
1771            .aggregate(Vec::<Expr>::new(), vec![max(col("b"))])?
1772            .build()?;
1773
1774        assert_optimized_plan_equal!(
1775            plan,
1776            @r"
1777        Aggregate: groupBy=[[]], aggr=[[max(test.b)]]
1778          Projection: test.b
1779            Filter: test.c > Int32(1)
1780              TableScan: test projection=[b, c]
1781        "
1782        )
1783    }
1784
1785    #[test]
1786    fn aggregate_with_periods() -> Result<()> {
1787        let schema = Schema::new(vec![Field::new("tag.one", DataType::Utf8, false)]);
1788
1789        // Build a plan that looks as follows (note "tag.one" is a column named
1790        // "tag.one", not a column named "one" in a table named "tag"):
1791        //
1792        // Projection: tag.one
1793        //   Aggregate: groupBy=[], aggr=[max("tag.one") AS "tag.one"]
1794        //    TableScan
1795        let plan = table_scan(Some("m4"), &schema, None)?
1796            .aggregate(
1797                Vec::<Expr>::new(),
1798                vec![max(col(Column::new_unqualified("tag.one"))).alias("tag.one")],
1799            )?
1800            .project([col(Column::new_unqualified("tag.one"))])?
1801            .build()?;
1802
1803        assert_optimized_plan_equal!(
1804            plan,
1805            @r"
1806        Aggregate: groupBy=[[]], aggr=[[max(m4.tag.one) AS tag.one]]
1807          TableScan: m4 projection=[tag.one]
1808        "
1809        )
1810    }
1811
1812    #[test]
1813    fn redundant_project() -> Result<()> {
1814        let table_scan = test_table_scan()?;
1815
1816        let plan = LogicalPlanBuilder::from(table_scan)
1817            .project(vec![col("a"), col("b"), col("c")])?
1818            .project(vec![col("a"), col("c"), col("b")])?
1819            .build()?;
1820        assert_optimized_plan_equal!(
1821            plan,
1822            @r"
1823        Projection: test.a, test.c, test.b
1824          TableScan: test projection=[a, b, c]
1825        "
1826        )
1827    }
1828
1829    #[test]
1830    fn reorder_scan() -> Result<()> {
1831        let schema = Schema::new(test_table_scan_fields());
1832
1833        let plan = table_scan(Some("test"), &schema, Some(vec![1, 0, 2]))?.build()?;
1834        assert_optimized_plan_equal!(
1835            plan,
1836            @"TableScan: test projection=[b, a, c]"
1837        )
1838    }
1839
1840    #[test]
1841    fn reorder_scan_projection() -> Result<()> {
1842        let schema = Schema::new(test_table_scan_fields());
1843
1844        let plan = table_scan(Some("test"), &schema, Some(vec![1, 0, 2]))?
1845            .project(vec![col("a"), col("b")])?
1846            .build()?;
1847        assert_optimized_plan_equal!(
1848            plan,
1849            @r"
1850        Projection: test.a, test.b
1851          TableScan: test projection=[b, a]
1852        "
1853        )
1854    }
1855
1856    #[test]
1857    fn reorder_projection() -> Result<()> {
1858        let table_scan = test_table_scan()?;
1859
1860        let plan = LogicalPlanBuilder::from(table_scan)
1861            .project(vec![col("c"), col("b"), col("a")])?
1862            .build()?;
1863        assert_optimized_plan_equal!(
1864            plan,
1865            @r"
1866        Projection: test.c, test.b, test.a
1867          TableScan: test projection=[a, b, c]
1868        "
1869        )
1870    }
1871
1872    #[test]
1873    fn noncontinuous_redundant_projection() -> Result<()> {
1874        let table_scan = test_table_scan()?;
1875
1876        let plan = LogicalPlanBuilder::from(table_scan)
1877            .project(vec![col("c"), col("b"), col("a")])?
1878            .filter(col("c").gt(lit(1)))?
1879            .project(vec![col("c"), col("a"), col("b")])?
1880            .filter(col("b").gt(lit(1)))?
1881            .filter(col("a").gt(lit(1)))?
1882            .project(vec![col("a"), col("c"), col("b")])?
1883            .build()?;
1884        assert_optimized_plan_equal!(
1885            plan,
1886            @r"
1887        Projection: test.a, test.c, test.b
1888          Filter: test.a > Int32(1)
1889            Filter: test.b > Int32(1)
1890              Projection: test.c, test.a, test.b
1891                Filter: test.c > Int32(1)
1892                  Projection: test.c, test.b, test.a
1893                    TableScan: test projection=[a, b, c]
1894        "
1895        )
1896    }
1897
1898    #[test]
1899    fn join_schema_trim_full_join_column_projection() -> Result<()> {
1900        let table_scan = test_table_scan()?;
1901
1902        let schema = Schema::new(vec![Field::new("c1", DataType::UInt32, false)]);
1903        let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
1904
1905        let plan = LogicalPlanBuilder::from(table_scan)
1906            .join(table2_scan, JoinType::Left, (vec!["a"], vec!["c1"]), None)?
1907            .project(vec![col("a"), col("b"), col("c1")])?
1908            .build()?;
1909
1910        let optimized_plan = optimize(plan)?;
1911
1912        // make sure projections are pushed down to both table scans
1913        assert_snapshot!(
1914            optimized_plan.clone(),
1915            @r"
1916        Left Join: test.a = test2.c1
1917          TableScan: test projection=[a, b]
1918          TableScan: test2 projection=[c1]
1919        "
1920        );
1921
1922        // make sure schema for join node include both join columns
1923        let optimized_join = optimized_plan;
1924        assert_eq!(
1925            **optimized_join.schema(),
1926            DFSchema::new_with_metadata(
1927                vec![
1928                    (
1929                        Some("test".into()),
1930                        Arc::new(Field::new("a", DataType::UInt32, false))
1931                    ),
1932                    (
1933                        Some("test".into()),
1934                        Arc::new(Field::new("b", DataType::UInt32, false))
1935                    ),
1936                    (
1937                        Some("test2".into()),
1938                        Arc::new(Field::new("c1", DataType::UInt32, true))
1939                    ),
1940                ],
1941                HashMap::new()
1942            )?,
1943        );
1944
1945        Ok(())
1946    }
1947
1948    #[test]
1949    fn join_schema_trim_partial_join_column_projection() -> Result<()> {
1950        // test join column push down without explicit column projections
1951
1952        let table_scan = test_table_scan()?;
1953
1954        let schema = Schema::new(vec![Field::new("c1", DataType::UInt32, false)]);
1955        let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
1956
1957        let plan = LogicalPlanBuilder::from(table_scan)
1958            .join(table2_scan, JoinType::Left, (vec!["a"], vec!["c1"]), None)?
1959            // projecting joined column `a` should push the right side column `c1` projection as
1960            // well into test2 table even though `c1` is not referenced in projection.
1961            .project(vec![col("a"), col("b")])?
1962            .build()?;
1963
1964        let optimized_plan = optimize(plan)?;
1965
1966        // make sure projections are pushed down to both table scans
1967        assert_snapshot!(
1968            optimized_plan.clone(),
1969            @r"
1970        Projection: test.a, test.b
1971          Left Join: test.a = test2.c1
1972            TableScan: test projection=[a, b]
1973            TableScan: test2 projection=[c1]
1974        "
1975        );
1976
1977        // make sure schema for join node include both join columns
1978        let optimized_join = optimized_plan.inputs()[0];
1979        assert_eq!(
1980            **optimized_join.schema(),
1981            DFSchema::new_with_metadata(
1982                vec![
1983                    (
1984                        Some("test".into()),
1985                        Arc::new(Field::new("a", DataType::UInt32, false))
1986                    ),
1987                    (
1988                        Some("test".into()),
1989                        Arc::new(Field::new("b", DataType::UInt32, false))
1990                    ),
1991                    (
1992                        Some("test2".into()),
1993                        Arc::new(Field::new("c1", DataType::UInt32, true))
1994                    ),
1995                ],
1996                HashMap::new()
1997            )?,
1998        );
1999
2000        Ok(())
2001    }
2002
2003    #[test]
2004    fn join_schema_trim_using_join() -> Result<()> {
2005        // shared join columns from using join should be pushed to both sides
2006
2007        let table_scan = test_table_scan()?;
2008
2009        let schema = Schema::new(vec![Field::new("a", DataType::UInt32, false)]);
2010        let table2_scan = scan_empty(Some("test2"), &schema, None)?.build()?;
2011
2012        let plan = LogicalPlanBuilder::from(table_scan)
2013            .join_using(table2_scan, JoinType::Left, vec!["a".into()])?
2014            .project(vec![col("a"), col("b")])?
2015            .build()?;
2016
2017        let optimized_plan = optimize(plan)?;
2018
2019        // make sure projections are pushed down to table scan
2020        assert_snapshot!(
2021            optimized_plan.clone(),
2022            @r"
2023        Projection: test.a, test.b
2024          Left Join: Using test.a = test2.a
2025            TableScan: test projection=[a, b]
2026            TableScan: test2 projection=[a]
2027        "
2028        );
2029
2030        // make sure schema for join node include both join columns
2031        let optimized_join = optimized_plan.inputs()[0];
2032        assert_eq!(
2033            **optimized_join.schema(),
2034            DFSchema::new_with_metadata(
2035                vec![
2036                    (
2037                        Some("test".into()),
2038                        Arc::new(Field::new("a", DataType::UInt32, false))
2039                    ),
2040                    (
2041                        Some("test".into()),
2042                        Arc::new(Field::new("b", DataType::UInt32, false))
2043                    ),
2044                    (
2045                        Some("test2".into()),
2046                        Arc::new(Field::new("a", DataType::UInt32, true))
2047                    ),
2048                ],
2049                HashMap::new()
2050            )?,
2051        );
2052
2053        Ok(())
2054    }
2055
2056    #[test]
2057    fn cast() -> Result<()> {
2058        let table_scan = test_table_scan()?;
2059
2060        let plan = LogicalPlanBuilder::from(table_scan)
2061            .project(vec![Expr::Cast(Cast::new(
2062                Box::new(col("c")),
2063                DataType::Float64,
2064            ))])?
2065            .build()?;
2066
2067        assert_optimized_plan_equal!(
2068            plan,
2069            @r"
2070        Projection: CAST(test.c AS Float64)
2071          TableScan: test projection=[c]
2072        "
2073        )
2074    }
2075
2076    #[test]
2077    fn table_scan_projected_schema() -> Result<()> {
2078        let table_scan = test_table_scan()?;
2079        let plan = LogicalPlanBuilder::from(test_table_scan()?)
2080            .project(vec![col("a"), col("b")])?
2081            .build()?;
2082
2083        assert_eq!(3, table_scan.schema().fields().len());
2084        assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2085        assert_fields_eq(&plan, vec!["a", "b"]);
2086
2087        assert_optimized_plan_equal!(
2088            plan,
2089            @"TableScan: test projection=[a, b]"
2090        )
2091    }
2092
2093    #[test]
2094    fn table_scan_projected_schema_non_qualified_relation() -> Result<()> {
2095        let table_scan = test_table_scan()?;
2096        let input_schema = table_scan.schema();
2097        assert_eq!(3, input_schema.fields().len());
2098        assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2099
2100        // Build the LogicalPlan directly (don't use PlanBuilder), so
2101        // that the Column references are unqualified (e.g. their
2102        // relation is `None`). PlanBuilder resolves the expressions
2103        let expr = vec![col("test.a"), col("test.b")];
2104        let plan =
2105            LogicalPlan::Projection(Projection::try_new(expr, Arc::new(table_scan))?);
2106
2107        assert_fields_eq(&plan, vec!["a", "b"]);
2108
2109        assert_optimized_plan_equal!(
2110            plan,
2111            @"TableScan: test projection=[a, b]"
2112        )
2113    }
2114
2115    #[test]
2116    fn table_limit() -> Result<()> {
2117        let table_scan = test_table_scan()?;
2118        assert_eq!(3, table_scan.schema().fields().len());
2119        assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2120
2121        let plan = LogicalPlanBuilder::from(table_scan)
2122            .project(vec![col("c"), col("a")])?
2123            .limit(0, Some(5))?
2124            .build()?;
2125
2126        assert_fields_eq(&plan, vec!["c", "a"]);
2127
2128        assert_optimized_plan_equal!(
2129            plan,
2130            @r"
2131        Limit: skip=0, fetch=5
2132          Projection: test.c, test.a
2133            TableScan: test projection=[a, c]
2134        "
2135        )
2136    }
2137
2138    #[test]
2139    fn table_scan_without_projection() -> Result<()> {
2140        let table_scan = test_table_scan()?;
2141        let plan = LogicalPlanBuilder::from(table_scan).build()?;
2142        // should expand projection to all columns without projection
2143        assert_optimized_plan_equal!(
2144            plan,
2145            @"TableScan: test projection=[a, b, c]"
2146        )
2147    }
2148
2149    #[test]
2150    fn table_scan_with_literal_projection() -> Result<()> {
2151        let table_scan = test_table_scan()?;
2152        let plan = LogicalPlanBuilder::from(table_scan)
2153            .project(vec![lit(1_i64), lit(2_i64)])?
2154            .build()?;
2155        assert_optimized_plan_equal!(
2156            plan,
2157            @r"
2158        Projection: Int64(1), Int64(2)
2159          TableScan: test projection=[]
2160        "
2161        )
2162    }
2163
2164    /// tests that it removes unused columns in projections
2165    #[test]
2166    fn table_unused_column() -> Result<()> {
2167        let table_scan = test_table_scan()?;
2168        assert_eq!(3, table_scan.schema().fields().len());
2169        assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2170
2171        // we never use "b" in the first projection => remove it
2172        let plan = LogicalPlanBuilder::from(table_scan)
2173            .project(vec![col("c"), col("a"), col("b")])?
2174            .filter(col("c").gt(lit(1)))?
2175            .aggregate(vec![col("c")], vec![max(col("a"))])?
2176            .build()?;
2177
2178        assert_fields_eq(&plan, vec!["c", "max(test.a)"]);
2179
2180        let plan = optimize(plan).expect("failed to optimize plan");
2181        assert_optimized_plan_equal!(
2182            plan,
2183            @r"
2184        Aggregate: groupBy=[[test.c]], aggr=[[max(test.a)]]
2185          Filter: test.c > Int32(1)
2186            Projection: test.c, test.a
2187              TableScan: test projection=[a, c]
2188        "
2189        )
2190    }
2191
2192    /// tests that it removes un-needed projections
2193    #[test]
2194    fn table_unused_projection() -> Result<()> {
2195        let table_scan = test_table_scan()?;
2196        assert_eq!(3, table_scan.schema().fields().len());
2197        assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2198
2199        // there is no need for the first projection
2200        let plan = LogicalPlanBuilder::from(table_scan)
2201            .project(vec![col("b")])?
2202            .project(vec![lit(1).alias("a")])?
2203            .build()?;
2204
2205        assert_fields_eq(&plan, vec!["a"]);
2206
2207        assert_optimized_plan_equal!(
2208            plan,
2209            @r"
2210        Projection: Int32(1) AS a
2211          TableScan: test projection=[]
2212        "
2213        )
2214    }
2215
2216    #[test]
2217    fn table_full_filter_pushdown() -> Result<()> {
2218        let schema = Schema::new(test_table_scan_fields());
2219
2220        let table_scan = table_scan_with_filters(
2221            Some("test"),
2222            &schema,
2223            None,
2224            vec![col("b").eq(lit(1))],
2225        )?
2226        .build()?;
2227        assert_eq!(3, table_scan.schema().fields().len());
2228        assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2229
2230        // there is no need for the first projection
2231        let plan = LogicalPlanBuilder::from(table_scan)
2232            .project(vec![col("b")])?
2233            .project(vec![lit(1).alias("a")])?
2234            .build()?;
2235
2236        assert_fields_eq(&plan, vec!["a"]);
2237
2238        assert_optimized_plan_equal!(
2239            plan,
2240            @r"
2241        Projection: Int32(1) AS a
2242          TableScan: test projection=[], full_filters=[b = Int32(1)]
2243        "
2244        )
2245    }
2246
2247    /// tests that optimizing twice yields same plan
2248    #[test]
2249    fn test_double_optimization() -> Result<()> {
2250        let table_scan = test_table_scan()?;
2251
2252        let plan = LogicalPlanBuilder::from(table_scan)
2253            .project(vec![col("b")])?
2254            .project(vec![lit(1).alias("a")])?
2255            .build()?;
2256
2257        let optimized_plan1 = optimize(plan).expect("failed to optimize plan");
2258        let optimized_plan2 =
2259            optimize(optimized_plan1.clone()).expect("failed to optimize plan");
2260
2261        let formatted_plan1 = format!("{optimized_plan1:?}");
2262        let formatted_plan2 = format!("{optimized_plan2:?}");
2263        assert_eq!(formatted_plan1, formatted_plan2);
2264        Ok(())
2265    }
2266
2267    #[test]
2268    fn test_continue_processing_through_extension() -> Result<()> {
2269        let table_scan = test_table_scan()?;
2270        let plan = LogicalPlanBuilder::from(table_scan.clone())
2271            .project(vec![col("a")])?
2272            .project(vec![col("a")])?
2273            .build()?;
2274        let plan = LogicalPlan::Extension(Extension {
2275            node: Arc::new(OpaqueRequirementsUserDefined {
2276                input: Arc::new(plan),
2277                schema: Arc::clone(table_scan.schema()),
2278            }),
2279        });
2280        let plan = optimize(plan).expect("failed to optimize plan");
2281        assert_optimized_plan_equal!(
2282            plan,
2283            @r"
2284        OpaqueRequirementsUserDefined
2285          TableScan: test projection=[a]
2286        "
2287        )
2288    }
2289
2290    /// tests that it removes an aggregate is never used downstream
2291    #[test]
2292    fn table_unused_aggregate() -> Result<()> {
2293        let table_scan = test_table_scan()?;
2294        assert_eq!(3, table_scan.schema().fields().len());
2295        assert_fields_eq(&table_scan, vec!["a", "b", "c"]);
2296
2297        // we never use "min(b)" => remove it
2298        let plan = LogicalPlanBuilder::from(table_scan)
2299            .aggregate(vec![col("a"), col("c")], vec![max(col("b")), min(col("b"))])?
2300            .filter(col("c").gt(lit(1)))?
2301            .project(vec![col("c"), col("a"), col("max(test.b)")])?
2302            .build()?;
2303
2304        assert_fields_eq(&plan, vec!["c", "a", "max(test.b)"]);
2305
2306        assert_optimized_plan_equal!(
2307            plan,
2308            @r"
2309        Projection: test.c, test.a, max(test.b)
2310          Filter: test.c > Int32(1)
2311            Aggregate: groupBy=[[test.a, test.c]], aggr=[[max(test.b)]]
2312              TableScan: test projection=[a, b, c]
2313        "
2314        )
2315    }
2316
2317    #[test]
2318    fn aggregate_filter_pushdown() -> Result<()> {
2319        let table_scan = test_table_scan()?;
2320        let aggr_with_filter = count_udaf()
2321            .call(vec![col("b")])
2322            .filter(col("c").gt(lit(42)))
2323            .build()?;
2324        let plan = LogicalPlanBuilder::from(table_scan)
2325            .aggregate(
2326                vec![col("a")],
2327                vec![count(col("b")), aggr_with_filter.alias("count2")],
2328            )?
2329            .build()?;
2330
2331        assert_optimized_plan_equal!(
2332            plan,
2333            @r"
2334        Aggregate: groupBy=[[test.a]], aggr=[[count(test.b), count(test.b) FILTER (WHERE test.c > Int32(42)) AS count2]]
2335          TableScan: test projection=[a, b, c]
2336        "
2337        )
2338    }
2339
2340    #[test]
2341    fn pushdown_through_distinct() -> Result<()> {
2342        let table_scan = test_table_scan()?;
2343
2344        let plan = LogicalPlanBuilder::from(table_scan)
2345            .project(vec![col("a"), col("b")])?
2346            .distinct()?
2347            .project(vec![col("a")])?
2348            .build()?;
2349
2350        assert_optimized_plan_equal!(
2351            plan,
2352            @r"
2353        Projection: test.a
2354          Distinct:
2355            TableScan: test projection=[a, b]
2356        "
2357        )
2358    }
2359
2360    #[test]
2361    fn test_window() -> Result<()> {
2362        let table_scan = test_table_scan()?;
2363
2364        let max1 = Expr::from(expr::WindowFunction::new(
2365            WindowFunctionDefinition::AggregateUDF(max_udaf()),
2366            vec![col("test.a")],
2367        ))
2368        .partition_by(vec![col("test.b")])
2369        .build()
2370        .unwrap();
2371
2372        let max2 = Expr::from(expr::WindowFunction::new(
2373            WindowFunctionDefinition::AggregateUDF(max_udaf()),
2374            vec![col("test.b")],
2375        ));
2376        let col1 = col(max1.schema_name().to_string());
2377        let col2 = col(max2.schema_name().to_string());
2378
2379        let plan = LogicalPlanBuilder::from(table_scan)
2380            .window(vec![max1])?
2381            .window(vec![max2])?
2382            .project(vec![col1, col2])?
2383            .build()?;
2384
2385        assert_optimized_plan_equal!(
2386            plan,
2387            @r"
2388        Projection: max(test.a) PARTITION BY [test.b] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING, max(test.b) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
2389          WindowAggr: windowExpr=[[max(test.b) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING]]
2390            Projection: test.b, max(test.a) PARTITION BY [test.b] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
2391              WindowAggr: windowExpr=[[max(test.a) PARTITION BY [test.b] ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING]]
2392                TableScan: test projection=[a, b]
2393        "
2394        )
2395    }
2396
2397    // Regression test for https://github.com/apache/datafusion/issues/20083
2398    // Optimizer must not fail when LeftMark joins from EXISTS OR EXISTS
2399    // feed into a Left join.
2400    #[test]
2401    fn optimize_projections_exists_or_exists_with_outer_join() -> Result<()> {
2402        use datafusion_expr::utils::disjunction;
2403        use datafusion_expr::{exists, out_ref_col};
2404
2405        let table_a = test_table_scan_with_name("a")?;
2406        let table_b = test_table_scan_with_name("b")?;
2407
2408        let sq_a = Arc::new(
2409            LogicalPlanBuilder::from(test_table_scan_with_name("sq_a")?)
2410                .filter(col("sq_a.a").eq(out_ref_col(DataType::UInt32, "a.a")))?
2411                .project(vec![lit(1)])?
2412                .build()?,
2413        );
2414
2415        let sq_b = Arc::new(
2416            LogicalPlanBuilder::from(test_table_scan_with_name("sq_b")?)
2417                .filter(col("sq_b.b").eq(out_ref_col(DataType::UInt32, "a.b")))?
2418                .project(vec![lit(1)])?
2419                .build()?,
2420        );
2421
2422        let plan = LogicalPlanBuilder::from(table_a)
2423            .filter(disjunction(vec![exists(sq_a), exists(sq_b)]).unwrap())?
2424            .join(table_b, JoinType::Left, (vec!["a"], vec!["a"]), None)?
2425            .build()?;
2426
2427        let optimizer = Optimizer::new();
2428        let config = OptimizerContext::new();
2429        optimizer.optimize(plan, &config, observe)?;
2430
2431        Ok(())
2432    }
2433
2434    #[test]
2435    fn optimize_projections_left_mark_join_with_projection() -> Result<()> {
2436        let table_a = test_table_scan_with_name("a")?;
2437        let table_b = test_table_scan_with_name("b")?;
2438        let table_c = test_table_scan_with_name("c")?;
2439
2440        let plan = LogicalPlanBuilder::from(table_a)
2441            .join(table_b, JoinType::LeftMark, (vec!["a"], vec!["a"]), None)?
2442            .project(vec![col("a.a"), col("a.b"), col("a.c")])?
2443            .join(table_c, JoinType::Left, (vec!["a"], vec!["a"]), None)?
2444            .build()?;
2445
2446        assert_optimized_plan_equal!(
2447            plan,
2448            @r"
2449        Left Join: a.a = c.a
2450          Projection: a.a, a.b, a.c
2451            LeftMark Join: a.a = b.a
2452              TableScan: a projection=[a, b, c]
2453              TableScan: b projection=[a]
2454          TableScan: c projection=[a, b, c]
2455        "
2456        )
2457    }
2458
2459    // Stacked filter-less LeftMark joins (from `= ANY` / `<> ALL`) must keep
2460    // each `mark` qualified so they don't collide.
2461    #[test]
2462    fn optimize_projections_stacked_mark_joins_keep_qualified_mark() -> Result<()> {
2463        let person = test_table_scan_with_name("person")?;
2464
2465        let aliased_scan = |table: &str, alias: &str| -> Result<LogicalPlan> {
2466            LogicalPlanBuilder::from(test_table_scan_with_name(table)?)
2467                .project(vec![col(format!("{table}.a"))])?
2468                .alias(alias)?
2469                .build()
2470        };
2471
2472        let plan = LogicalPlanBuilder::from(person)
2473            .join_on(
2474                aliased_scan("s1", "__correlated_sq_1")?,
2475                JoinType::LeftMark,
2476                vec![lit(true)],
2477            )?
2478            .join_on(
2479                aliased_scan("s2", "__correlated_sq_2")?,
2480                JoinType::LeftMark,
2481                vec![lit(true)],
2482            )?
2483            .join_on(
2484                aliased_scan("s3", "__correlated_sq_3")?,
2485                JoinType::LeftMark,
2486                vec![lit(true)],
2487            )?
2488            .filter(
2489                col("__correlated_sq_1.mark")
2490                    .or(col("__correlated_sq_2.mark"))
2491                    .and(not(col("__correlated_sq_3.mark"))),
2492            )?
2493            .project(vec![col("person.a")])?
2494            .build()?;
2495
2496        assert_optimized_plan_equal!(
2497            plan,
2498            @r"
2499        Projection: person.a
2500          Filter: (__correlated_sq_1.mark OR __correlated_sq_2.mark) AND NOT __correlated_sq_3.mark
2501            LeftMark Join:  Filter: Boolean(true)
2502              LeftMark Join:  Filter: Boolean(true)
2503                LeftMark Join:  Filter: Boolean(true)
2504                  TableScan: person projection=[a]
2505                  SubqueryAlias: __correlated_sq_1
2506                    TableScan: s1 projection=[a]
2507                SubqueryAlias: __correlated_sq_2
2508                  TableScan: s2 projection=[a]
2509              SubqueryAlias: __correlated_sq_3
2510                TableScan: s3 projection=[a]
2511        "
2512        )
2513    }
2514
2515    fn observe(_plan: &LogicalPlan, _rule: &dyn OptimizerRule) {}
2516
2517    fn optimize(plan: LogicalPlan) -> Result<LogicalPlan> {
2518        let optimizer = Optimizer::with_rules(vec![Arc::new(OptimizeProjections::new())]);
2519        let optimized_plan =
2520            optimizer.optimize(plan, &OptimizerContext::new(), observe)?;
2521        Ok(optimized_plan)
2522    }
2523}