Skip to main content

datafusion/
physical_planner.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//! Planner for [`LogicalPlan`] to [`ExecutionPlan`]
19
20use std::borrow::Cow;
21use std::collections::{HashMap, HashSet};
22use std::sync::Arc;
23
24use crate::datasource::file_format::file_type_to_format;
25use crate::datasource::listing::ListingTableUrl;
26use crate::datasource::physical_plan::{FileOutputMode, FileSinkConfig};
27use crate::datasource::{DefaultTableSource, source_as_provider};
28use crate::error::{DataFusionError, Result};
29use crate::execution::context::{ExecutionProps, SessionState};
30use crate::logical_expr::utils::generate_sort_key;
31use crate::logical_expr::{
32    Aggregate, EmptyRelation, Join, Projection, Sort, TableScan, Unnest, Values, Window,
33};
34use crate::logical_expr::{
35    Expr, LogicalPlan, Partitioning as LogicalPartitioning, PlanType, Repartition,
36    UserDefinedLogicalNode,
37};
38use crate::physical_expr::{create_physical_expr, create_physical_exprs};
39use crate::physical_plan::aggregates::{AggregateExec, AggregateMode, PhysicalGroupBy};
40use crate::physical_plan::analyze::AnalyzeExec;
41use crate::physical_plan::explain::ExplainExec;
42use crate::physical_plan::filter::FilterExecBuilder;
43use crate::physical_plan::joins::utils as join_utils;
44use crate::physical_plan::joins::{
45    CrossJoinExec, HashJoinExec, NestedLoopJoinExec, PartitionMode, SortMergeJoinExec,
46};
47use crate::physical_plan::limit::{GlobalLimitExec, LocalLimitExec};
48use crate::physical_plan::projection::{ProjectionExec, ProjectionExpr};
49use crate::physical_plan::repartition::RepartitionExec;
50use crate::physical_plan::sorts::sort::SortExec;
51use crate::physical_plan::union::UnionExec;
52use crate::physical_plan::unnest::UnnestExec;
53use crate::physical_plan::windows::{BoundedWindowAggExec, WindowAggExec};
54use crate::physical_plan::{
55    ExecutionPlan, ExecutionPlanProperties, InputOrderMode, Partitioning, PhysicalExpr,
56    WindowExpr, displayable, windows,
57};
58use crate::schema_equivalence::schema_satisfied_by;
59
60use arrow::array::{RecordBatch, builder::StringBuilder};
61use arrow::compute::SortOptions;
62use arrow::datatypes::Schema;
63use arrow_schema::Field;
64use datafusion_catalog::ScanArgs;
65use datafusion_common::Column;
66use datafusion_common::display::ToStringifiedPlan;
67use datafusion_common::format::ExplainAnalyzeLevel;
68use datafusion_common::tree_node::{
69    Transformed, TreeNode, TreeNodeRecursion, TreeNodeVisitor,
70};
71use datafusion_common::{
72    DFSchema, DFSchemaRef, ScalarValue, exec_err, internal_datafusion_err, internal_err,
73    not_impl_err, plan_err,
74};
75use datafusion_common::{
76    TableReference, assert_eq_or_internal_err, assert_or_internal_err,
77};
78use datafusion_datasource::file_groups::FileGroup;
79use datafusion_datasource::memory::MemorySourceConfig;
80use datafusion_expr::dml::{CopyTo, InsertOp};
81use datafusion_expr::expr::{
82    AggregateFunction, AggregateFunctionParams, Alias, GroupingSet, NullTreatment,
83    WindowFunction, WindowFunctionParams, physical_name,
84};
85use datafusion_expr::expr_rewriter::unnormalize_cols;
86use datafusion_expr::logical_plan::builder::wrap_projection_for_join_if_necessary;
87use datafusion_expr::utils::{expr_to_columns, split_conjunction};
88use datafusion_expr::{
89    Analyze, BinaryExpr, DescribeTable, DmlStatement, Explain, ExplainFormat, Extension,
90    FetchType, Filter, JoinType, Operator, RecursiveQuery, SkipType, StringifiedPlan,
91    WindowFrame, WindowFrameBound, WriteOp,
92};
93use datafusion_physical_expr::aggregate::{AggregateExprBuilder, AggregateFunctionExpr};
94use datafusion_physical_expr::expressions::Literal;
95use datafusion_physical_expr::{
96    LexOrdering, PhysicalSortExpr, create_physical_sort_exprs,
97};
98use datafusion_physical_optimizer::PhysicalOptimizerRule;
99use datafusion_physical_plan::empty::EmptyExec;
100use datafusion_physical_plan::execution_plan::InvariantLevel;
101use datafusion_physical_plan::joins::PiecewiseMergeJoinExec;
102use datafusion_physical_plan::metrics::MetricType;
103use datafusion_physical_plan::placeholder_row::PlaceholderRowExec;
104use datafusion_physical_plan::recursive_query::RecursiveQueryExec;
105use datafusion_physical_plan::unnest::ListUnnest;
106
107use async_trait::async_trait;
108use datafusion_physical_plan::async_func::{AsyncFuncExec, AsyncMapper};
109use futures::{StreamExt, TryStreamExt};
110use itertools::{Itertools, multiunzip};
111use log::debug;
112use tokio::sync::Mutex;
113
114/// Physical query planner that converts a `LogicalPlan` to an
115/// `ExecutionPlan` suitable for execution.
116#[async_trait]
117pub trait PhysicalPlanner: Send + Sync {
118    /// Create a physical plan from a logical plan
119    async fn create_physical_plan(
120        &self,
121        logical_plan: &LogicalPlan,
122        session_state: &SessionState,
123    ) -> Result<Arc<dyn ExecutionPlan>>;
124
125    /// Create a physical expression from a logical expression
126    /// suitable for evaluation
127    ///
128    /// `expr`: the expression to convert
129    ///
130    /// `input_dfschema`: the logical plan schema for evaluating `expr`
131    fn create_physical_expr(
132        &self,
133        expr: &Expr,
134        input_dfschema: &DFSchema,
135        session_state: &SessionState,
136    ) -> Result<Arc<dyn PhysicalExpr>>;
137}
138
139/// This trait exposes the ability to plan an [`ExecutionPlan`] out of a [`LogicalPlan`].
140#[async_trait]
141pub trait ExtensionPlanner {
142    /// Create a physical plan for a [`UserDefinedLogicalNode`].
143    ///
144    /// `input_dfschema`: the logical plan schema for the inputs to this node
145    ///
146    /// Returns an error when the planner knows how to plan the concrete
147    /// implementation of `node` but errors while doing so.
148    ///
149    /// Returns `None` when the planner does not know how to plan the
150    /// `node` and wants to delegate the planning to another
151    /// [`ExtensionPlanner`].
152    async fn plan_extension(
153        &self,
154        planner: &dyn PhysicalPlanner,
155        node: &dyn UserDefinedLogicalNode,
156        logical_inputs: &[&LogicalPlan],
157        physical_inputs: &[Arc<dyn ExecutionPlan>],
158        session_state: &SessionState,
159    ) -> Result<Option<Arc<dyn ExecutionPlan>>>;
160
161    /// Create a physical plan for a [`LogicalPlan::TableScan`].
162    ///
163    /// This is useful for planning valid [`TableSource`]s that are not [`TableProvider`]s.
164    ///
165    /// Returns:
166    /// * `Ok(Some(plan))` if the planner knows how to plan the `scan`
167    /// * `Ok(None)` if the planner does not know how to plan the `scan` and wants to delegate the planning to another [`ExtensionPlanner`]
168    /// * `Err` if the planner knows how to plan the `scan` but errors while doing so
169    ///
170    /// # Example
171    ///
172    /// ```rust,ignore
173    /// use std::sync::Arc;
174    /// use datafusion::physical_plan::ExecutionPlan;
175    /// use datafusion::logical_expr::TableScan;
176    /// use datafusion::execution::context::SessionState;
177    /// use datafusion::error::Result;
178    /// use datafusion_physical_planner::{ExtensionPlanner, PhysicalPlanner};
179    /// use async_trait::async_trait;
180    ///
181    /// // Your custom table source type
182    /// struct MyCustomTableSource { /* ... */ }
183    ///
184    /// // Your custom execution plan
185    /// struct MyCustomExec { /* ... */ }
186    ///
187    /// struct MyExtensionPlanner;
188    ///
189    /// #[async_trait]
190    /// impl ExtensionPlanner for MyExtensionPlanner {
191    ///     async fn plan_extension(
192    ///         &self,
193    ///         _planner: &dyn PhysicalPlanner,
194    ///         _node: &dyn UserDefinedLogicalNode,
195    ///         _logical_inputs: &[&LogicalPlan],
196    ///         _physical_inputs: &[Arc<dyn ExecutionPlan>],
197    ///         _session_state: &SessionState,
198    ///     ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
199    ///         Ok(None)
200    ///     }
201    ///
202    ///     async fn plan_table_scan(
203    ///         &self,
204    ///         _planner: &dyn PhysicalPlanner,
205    ///         scan: &TableScan,
206    ///         _session_state: &SessionState,
207    ///     ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
208    ///         // Check if this is your custom table source
209    ///         if scan.source.as_any().is::<MyCustomTableSource>() {
210    ///             // Create a custom execution plan for your table source
211    ///             let exec = MyCustomExec::new(
212    ///                 scan.table_name.clone(),
213    ///                 Arc::clone(scan.projected_schema.inner()),
214    ///             );
215    ///             Ok(Some(Arc::new(exec)))
216    ///         } else {
217    ///             // Return None to let other extension planners handle it
218    ///             Ok(None)
219    ///         }
220    ///     }
221    /// }
222    /// ```
223    ///
224    /// [`TableSource`]: datafusion_expr::TableSource
225    /// [`TableProvider`]: datafusion_catalog::TableProvider
226    async fn plan_table_scan(
227        &self,
228        _planner: &dyn PhysicalPlanner,
229        _scan: &TableScan,
230        _session_state: &SessionState,
231    ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
232        Ok(None)
233    }
234}
235
236/// Default single node physical query planner that converts a
237/// `LogicalPlan` to an `ExecutionPlan` suitable for execution.
238///
239/// This planner will first flatten the `LogicalPlan` tree via a
240/// depth first approach, which allows it to identify the leaves
241/// of the tree.
242///
243/// Tasks are spawned from these leaves and traverse back up the
244/// tree towards the root, converting each `LogicalPlan` node it
245/// reaches into their equivalent `ExecutionPlan` node. When these
246/// tasks reach a common node, they will terminate until the last
247/// task reaches the node which will then continue building up the
248/// tree.
249///
250/// Up to [`planning_concurrency`] tasks are buffered at once to
251/// execute concurrently.
252///
253/// [`planning_concurrency`]: crate::config::ExecutionOptions::planning_concurrency
254#[derive(Default)]
255pub struct DefaultPhysicalPlanner {
256    extension_planners: Vec<Arc<dyn ExtensionPlanner + Send + Sync>>,
257}
258
259#[async_trait]
260impl PhysicalPlanner for DefaultPhysicalPlanner {
261    /// Create a physical plan from a logical plan
262    async fn create_physical_plan(
263        &self,
264        logical_plan: &LogicalPlan,
265        session_state: &SessionState,
266    ) -> Result<Arc<dyn ExecutionPlan>> {
267        if let Some(plan) = self
268            .handle_explain_or_analyze(logical_plan, session_state)
269            .await?
270        {
271            return Ok(plan);
272        }
273        let plan = self
274            .create_initial_plan(logical_plan, session_state)
275            .await?;
276
277        self.optimize_physical_plan(plan, session_state, |_, _| {})
278    }
279
280    /// Create a physical expression from a logical expression
281    /// suitable for evaluation
282    ///
283    /// `e`: the expression to convert
284    ///
285    /// `input_dfschema`: the logical plan schema for evaluating `e`
286    fn create_physical_expr(
287        &self,
288        expr: &Expr,
289        input_dfschema: &DFSchema,
290        session_state: &SessionState,
291    ) -> Result<Arc<dyn PhysicalExpr>> {
292        create_physical_expr(expr, input_dfschema, session_state.execution_props())
293    }
294}
295
296#[derive(Debug)]
297struct ExecutionPlanChild {
298    /// Index needed to order children of parent to ensure consistency with original
299    /// `LogicalPlan`
300    index: usize,
301    plan: Arc<dyn ExecutionPlan>,
302}
303
304#[derive(Debug)]
305enum NodeState {
306    ZeroOrOneChild,
307    /// Nodes with multiple children will have multiple tasks accessing it,
308    /// and each task will append their contribution until the last task takes
309    /// all the children to build the parent node.
310    TwoOrMoreChildren(Mutex<Vec<ExecutionPlanChild>>),
311}
312
313/// To avoid needing to pass single child wrapped in a Vec for nodes
314/// with only one child.
315enum ChildrenContainer {
316    None,
317    One(Arc<dyn ExecutionPlan>),
318    Multiple(Vec<Arc<dyn ExecutionPlan>>),
319}
320
321impl ChildrenContainer {
322    fn one(self) -> Result<Arc<dyn ExecutionPlan>> {
323        match self {
324            Self::One(p) => Ok(p),
325            _ => internal_err!("More than one child in ChildrenContainer"),
326        }
327    }
328
329    fn two(self) -> Result<[Arc<dyn ExecutionPlan>; 2]> {
330        match self {
331            Self::Multiple(v) if v.len() == 2 => Ok(v.try_into().unwrap()),
332            _ => internal_err!("ChildrenContainer doesn't contain exactly 2 children"),
333        }
334    }
335
336    fn vec(self) -> Vec<Arc<dyn ExecutionPlan>> {
337        match self {
338            Self::None => vec![],
339            Self::One(p) => vec![p],
340            Self::Multiple(v) => v,
341        }
342    }
343}
344
345#[derive(Debug)]
346struct LogicalNode<'a> {
347    node: &'a LogicalPlan,
348    // None if root
349    parent_index: Option<usize>,
350    state: NodeState,
351}
352
353impl DefaultPhysicalPlanner {
354    /// Create a physical planner that uses `extension_planners` to
355    /// plan user-defined logical nodes [`LogicalPlan::Extension`]
356    /// or user-defined table sources in [`LogicalPlan::TableScan`].
357    /// The planner uses the first [`ExtensionPlanner`] to return a non-`None`
358    /// plan.
359    pub fn with_extension_planners(
360        extension_planners: Vec<Arc<dyn ExtensionPlanner + Send + Sync>>,
361    ) -> Self {
362        Self { extension_planners }
363    }
364
365    fn ensure_schema_matches(
366        &self,
367        logical_schema: &DFSchemaRef,
368        physical_plan: &Arc<dyn ExecutionPlan>,
369        context: &str,
370    ) -> Result<()> {
371        if !logical_schema.matches_arrow_schema(&physical_plan.schema()) {
372            return plan_err!(
373                "{} created an ExecutionPlan with mismatched schema. \
374                    LogicalPlan schema: {:?}, ExecutionPlan schema: {:?}",
375                context,
376                logical_schema,
377                physical_plan.schema()
378            );
379        }
380        Ok(())
381    }
382
383    /// Create a physical plan from a logical plan
384    async fn create_initial_plan(
385        &self,
386        logical_plan: &LogicalPlan,
387        session_state: &SessionState,
388    ) -> Result<Arc<dyn ExecutionPlan>> {
389        // DFS the tree to flatten it into a Vec.
390        // This will allow us to build the Physical Plan from the leaves up
391        // to avoid recursion, and also to make it easier to build a valid
392        // Physical Plan from the start and not rely on some intermediate
393        // representation (since parents need to know their children at
394        // construction time).
395        let mut flat_tree = vec![];
396        let mut dfs_visit_stack = vec![(None, logical_plan)];
397        // Use this to be able to find the leaves to start construction bottom
398        // up concurrently.
399        let mut flat_tree_leaf_indices = vec![];
400        while let Some((parent_index, node)) = dfs_visit_stack.pop() {
401            let current_index = flat_tree.len();
402            // Because of how we extend the visit stack here, we visit the children
403            // in reverse order of how they appear, so later we need to reverse
404            // the order of children when building the nodes.
405            dfs_visit_stack
406                .extend(node.inputs().iter().map(|&n| (Some(current_index), n)));
407            let state = match node.inputs().len() {
408                0 => {
409                    flat_tree_leaf_indices.push(current_index);
410                    NodeState::ZeroOrOneChild
411                }
412                1 => NodeState::ZeroOrOneChild,
413                _ => {
414                    let ready_children = Vec::with_capacity(node.inputs().len());
415                    let ready_children = Mutex::new(ready_children);
416                    NodeState::TwoOrMoreChildren(ready_children)
417                }
418            };
419            let node = LogicalNode {
420                node,
421                parent_index,
422                state,
423            };
424            flat_tree.push(node);
425        }
426        let flat_tree = Arc::new(flat_tree);
427
428        let planning_concurrency = session_state
429            .config_options()
430            .execution
431            .planning_concurrency;
432        // Can never spawn more tasks than leaves in the tree, as these tasks must
433        // all converge down to the root node, which can only be processed by a
434        // single task.
435        let max_concurrency = planning_concurrency.min(flat_tree_leaf_indices.len());
436
437        // Spawning tasks which will traverse leaf up to the root.
438        let tasks = flat_tree_leaf_indices
439            .into_iter()
440            .map(|index| self.task_helper(index, Arc::clone(&flat_tree), session_state));
441        let mut outputs = futures::stream::iter(tasks)
442            .buffer_unordered(max_concurrency)
443            .try_collect::<Vec<_>>()
444            .await?
445            .into_iter()
446            .flatten()
447            .collect::<Vec<_>>();
448        // Ideally this never happens if we have a valid LogicalPlan tree
449        assert_eq_or_internal_err!(
450            outputs.len(),
451            1,
452            "Failed to convert LogicalPlan to ExecutionPlan: More than one root detected"
453        );
454        let plan = outputs.pop().unwrap();
455        Ok(plan)
456    }
457
458    /// These tasks start at a leaf and traverse up the tree towards the root, building
459    /// an ExecutionPlan as they go. When they reach a node with two or more children,
460    /// they append their current result (a child of the parent node) to the children
461    /// vector, and if this is sufficient to create the parent then continues traversing
462    /// the tree to create nodes. Otherwise, the task terminates.
463    async fn task_helper<'a>(
464        &'a self,
465        leaf_starter_index: usize,
466        flat_tree: Arc<Vec<LogicalNode<'a>>>,
467        session_state: &'a SessionState,
468    ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
469        // We always start with a leaf, so can ignore status and pass empty children
470        let mut node = flat_tree.get(leaf_starter_index).ok_or_else(|| {
471            internal_datafusion_err!(
472                "Invalid index whilst creating initial physical plan"
473            )
474        })?;
475        let mut plan = self
476            .map_logical_node_to_physical(
477                node.node,
478                session_state,
479                ChildrenContainer::None,
480            )
481            .await?;
482        let mut current_index = leaf_starter_index;
483        // parent_index is None only for root
484        while let Some(parent_index) = node.parent_index {
485            node = flat_tree.get(parent_index).ok_or_else(|| {
486                internal_datafusion_err!(
487                    "Invalid index whilst creating initial physical plan"
488                )
489            })?;
490            match &node.state {
491                NodeState::ZeroOrOneChild => {
492                    plan = self
493                        .map_logical_node_to_physical(
494                            node.node,
495                            session_state,
496                            ChildrenContainer::One(plan),
497                        )
498                        .await?;
499                }
500                // See if we have all children to build the node.
501                NodeState::TwoOrMoreChildren(children) => {
502                    let mut children: Vec<ExecutionPlanChild> = {
503                        let mut guard = children.lock().await;
504                        // Add our contribution to this parent node.
505                        // Vec is pre-allocated so no allocation should occur here.
506                        guard.push(ExecutionPlanChild {
507                            index: current_index,
508                            plan,
509                        });
510                        if guard.len() < node.node.inputs().len() {
511                            // This node is not ready yet, still pending more children.
512                            // This task is finished forever.
513                            return Ok(None);
514                        }
515
516                        // With this task's contribution we have enough children.
517                        // This task is the only one building this node now, and thus
518                        // no other task will need the Mutex for this node, so take
519                        // all children.
520                        std::mem::take(guard.as_mut())
521                    };
522
523                    // Indices refer to position in flat tree Vec, which means they are
524                    // guaranteed to be unique, hence unstable sort used.
525                    //
526                    // We reverse sort because of how we visited the node in the initial
527                    // DFS traversal (see above).
528                    children.sort_unstable_by_key(|epc| std::cmp::Reverse(epc.index));
529                    let children = children.into_iter().map(|epc| epc.plan).collect();
530                    let children = ChildrenContainer::Multiple(children);
531                    plan = self
532                        .map_logical_node_to_physical(node.node, session_state, children)
533                        .await?;
534                }
535            }
536            current_index = parent_index;
537        }
538        // Only one task should ever reach this point for a valid LogicalPlan tree.
539        Ok(Some(plan))
540    }
541
542    /// Given a single LogicalPlan node, map it to its physical ExecutionPlan counterpart.
543    async fn map_logical_node_to_physical(
544        &self,
545        node: &LogicalPlan,
546        session_state: &SessionState,
547        children: ChildrenContainer,
548    ) -> Result<Arc<dyn ExecutionPlan>> {
549        let exec_node: Arc<dyn ExecutionPlan> = match node {
550            // Leaves (no children)
551            LogicalPlan::TableScan(scan) => {
552                let TableScan {
553                    source,
554                    projection,
555                    filters,
556                    fetch,
557                    projected_schema,
558                    ..
559                } = scan;
560
561                if let Ok(source) = source_as_provider(source) {
562                    // Remove all qualifiers from the scan as the provider
563                    // doesn't know (nor should care) how the relation was
564                    // referred to in the query
565                    let filters = unnormalize_cols(filters.iter().cloned());
566                    let filters_vec = filters.into_iter().collect::<Vec<_>>();
567                    let opts = ScanArgs::default()
568                        .with_projection(projection.as_deref())
569                        .with_filters(Some(&filters_vec))
570                        .with_limit(*fetch);
571                    let res = source.scan_with_args(session_state, opts).await?;
572                    Arc::clone(res.plan())
573                } else {
574                    let mut maybe_plan = None;
575                    for planner in &self.extension_planners {
576                        if maybe_plan.is_some() {
577                            break;
578                        }
579
580                        maybe_plan =
581                            planner.plan_table_scan(self, scan, session_state).await?;
582                    }
583
584                    let plan = match maybe_plan {
585                        Some(plan) => plan,
586                        None => {
587                            return plan_err!(
588                                "No installed planner was able to plan TableScan for custom TableSource: {:?}",
589                                scan.table_name
590                            );
591                        }
592                    };
593                    let context =
594                        format!("Extension planner for table scan {}", scan.table_name);
595                    self.ensure_schema_matches(projected_schema, &plan, &context)?;
596                    plan
597                }
598            }
599            LogicalPlan::Values(Values { values, schema }) => {
600                let exprs = values
601                    .iter()
602                    .map(|row| {
603                        row.iter()
604                            .map(|expr| {
605                                self.create_physical_expr(expr, schema, session_state)
606                            })
607                            .collect::<Result<Vec<Arc<dyn PhysicalExpr>>>>()
608                    })
609                    .collect::<Result<Vec<_>>>()?;
610                MemorySourceConfig::try_new_as_values(Arc::clone(schema.inner()), exprs)?
611                    as _
612            }
613            LogicalPlan::EmptyRelation(EmptyRelation {
614                produce_one_row: false,
615                schema,
616            }) => Arc::new(EmptyExec::new(Arc::clone(schema.inner()))),
617            LogicalPlan::EmptyRelation(EmptyRelation {
618                produce_one_row: true,
619                schema,
620            }) => Arc::new(PlaceholderRowExec::new(Arc::clone(schema.inner()))),
621            LogicalPlan::DescribeTable(DescribeTable {
622                schema,
623                output_schema,
624            }) => {
625                let output_schema = Arc::clone(output_schema.inner());
626                self.plan_describe(&Arc::clone(schema), output_schema)?
627            }
628
629            // 1 Child
630            LogicalPlan::Copy(CopyTo {
631                input,
632                output_url,
633                file_type,
634                partition_by,
635                options: source_option_tuples,
636                output_schema: _,
637            }) => {
638                let original_url = output_url.clone();
639                let input_exec = children.one()?;
640                let parsed_url = ListingTableUrl::parse(output_url)?;
641                let object_store_url = parsed_url.object_store();
642
643                let schema = Arc::clone(input.schema().inner());
644
645                // Note: the DataType passed here is ignored for the purposes of writing and inferred instead
646                // from the schema of the RecordBatch being written. This allows COPY statements to specify only
647                // the column name rather than column name + explicit data type.
648                let table_partition_cols = partition_by
649                    .iter()
650                    .map(|s| (s.to_string(), arrow::datatypes::DataType::Null))
651                    .collect::<Vec<_>>();
652
653                let keep_partition_by_columns = match source_option_tuples
654                    .get("execution.keep_partition_by_columns")
655                    .map(|v| v.trim())
656                {
657                    None => {
658                        session_state
659                            .config()
660                            .options()
661                            .execution
662                            .keep_partition_by_columns
663                    }
664                    Some("true") => true,
665                    Some("false") => false,
666                    Some(value) => {
667                        return Err(DataFusionError::Configuration(format!(
668                            "provided value for 'execution.keep_partition_by_columns' was not recognized: \"{value}\""
669                        )));
670                    }
671                };
672
673                // Parse single_file_output option if explicitly set
674                let file_output_mode = match source_option_tuples
675                    .get("single_file_output")
676                    .map(|v| v.trim())
677                {
678                    None => FileOutputMode::Automatic,
679                    Some("true") => FileOutputMode::SingleFile,
680                    Some("false") => FileOutputMode::Directory,
681                    Some(value) => {
682                        return Err(DataFusionError::Configuration(format!(
683                            "provided value for 'single_file_output' was not recognized: \"{value}\""
684                        )));
685                    }
686                };
687
688                // Filter out sink-related options that are not format options
689                let format_options: HashMap<String, String> = source_option_tuples
690                    .iter()
691                    .filter(|(k, _)| k.as_str() != "single_file_output")
692                    .map(|(k, v)| (k.clone(), v.clone()))
693                    .collect();
694
695                let sink_format = file_type_to_format(file_type)?
696                    .create(session_state, &format_options)?;
697
698                // Determine extension based on format extension and compression
699                let file_extension = match sink_format.compression_type() {
700                    Some(compression_type) => sink_format
701                        .get_ext_with_compression(&compression_type)
702                        .unwrap_or_else(|_| sink_format.get_ext()),
703                    None => sink_format.get_ext(),
704                };
705
706                // Set file sink related options
707                let config = FileSinkConfig {
708                    original_url,
709                    object_store_url,
710                    table_paths: vec![parsed_url],
711                    file_group: FileGroup::default(),
712                    output_schema: schema,
713                    table_partition_cols,
714                    insert_op: InsertOp::Append,
715                    keep_partition_by_columns,
716                    file_extension,
717                    file_output_mode,
718                };
719
720                let ordering = input_exec.properties().output_ordering().cloned();
721
722                sink_format
723                    .create_writer_physical_plan(
724                        input_exec,
725                        session_state,
726                        config,
727                        ordering.map(Into::into),
728                    )
729                    .await?
730            }
731            LogicalPlan::Dml(DmlStatement {
732                target,
733                op: WriteOp::Insert(insert_op),
734                ..
735            }) => {
736                if let Some(provider) =
737                    target.as_any().downcast_ref::<DefaultTableSource>()
738                {
739                    let input_exec = children.one()?;
740                    provider
741                        .table_provider
742                        .insert_into(session_state, input_exec, *insert_op)
743                        .await?
744                } else {
745                    return exec_err!(
746                        "Table source can't be downcasted to DefaultTableSource"
747                    );
748                }
749            }
750            LogicalPlan::Dml(DmlStatement {
751                table_name,
752                target,
753                op: WriteOp::Delete,
754                input,
755                ..
756            }) => {
757                if let Some(provider) =
758                    target.as_any().downcast_ref::<DefaultTableSource>()
759                {
760                    let filters = extract_dml_filters(input, table_name)?;
761                    provider
762                        .table_provider
763                        .delete_from(session_state, filters)
764                        .await
765                        .map_err(|e| {
766                            e.context(format!("DELETE operation on table '{table_name}'"))
767                        })?
768                } else {
769                    return exec_err!(
770                        "Table source can't be downcasted to DefaultTableSource"
771                    );
772                }
773            }
774            LogicalPlan::Dml(DmlStatement {
775                table_name,
776                target,
777                op: WriteOp::Update,
778                input,
779                ..
780            }) => {
781                if let Some(provider) =
782                    target.as_any().downcast_ref::<DefaultTableSource>()
783                {
784                    // For UPDATE, the assignments are encoded in the projection of input
785                    // We pass the filters and let the provider handle the projection
786                    let filters = extract_dml_filters(input, table_name)?;
787                    // Extract assignments from the projection in input plan
788                    let assignments = extract_update_assignments(input)?;
789                    provider
790                        .table_provider
791                        .update(session_state, assignments, filters)
792                        .await
793                        .map_err(|e| {
794                            e.context(format!("UPDATE operation on table '{table_name}'"))
795                        })?
796                } else {
797                    return exec_err!(
798                        "Table source can't be downcasted to DefaultTableSource"
799                    );
800                }
801            }
802            LogicalPlan::Dml(DmlStatement {
803                table_name,
804                target,
805                op: WriteOp::Truncate,
806                ..
807            }) => {
808                if let Some(provider) =
809                    target.as_any().downcast_ref::<DefaultTableSource>()
810                {
811                    provider
812                        .table_provider
813                        .truncate(session_state)
814                        .await
815                        .map_err(|e| {
816                            e.context(format!(
817                                "TRUNCATE operation on table '{table_name}'"
818                            ))
819                        })?
820                } else {
821                    return exec_err!(
822                        "Table source can't be downcasted to DefaultTableSource"
823                    );
824                }
825            }
826            LogicalPlan::Window(Window { window_expr, .. }) => {
827                assert_or_internal_err!(
828                    !window_expr.is_empty(),
829                    "Impossibly got empty window expression"
830                );
831
832                let input_exec = children.one()?;
833
834                let get_sort_keys = |expr: &Expr| match expr {
835                    Expr::WindowFunction(window_fun) => {
836                        let WindowFunctionParams {
837                            partition_by,
838                            order_by,
839                            ..
840                        } = &window_fun.as_ref().params;
841                        generate_sort_key(partition_by, order_by)
842                    }
843                    Expr::Alias(Alias { expr, .. }) => {
844                        // Convert &Box<T> to &T
845                        match &**expr {
846                            Expr::WindowFunction(window_fun) => {
847                                let WindowFunctionParams {
848                                    partition_by,
849                                    order_by,
850                                    ..
851                                } = &window_fun.as_ref().params;
852                                generate_sort_key(partition_by, order_by)
853                            }
854                            _ => unreachable!(),
855                        }
856                    }
857                    _ => unreachable!(),
858                };
859                let sort_keys = get_sort_keys(&window_expr[0])?;
860                if window_expr.len() > 1 {
861                    debug_assert!(
862                        window_expr[1..]
863                            .iter()
864                            .all(|expr| get_sort_keys(expr).unwrap() == sort_keys),
865                        "all window expressions shall have the same sort keys, as guaranteed by logical planning"
866                    );
867                }
868
869                let logical_schema = node.schema();
870                let window_expr = window_expr
871                    .iter()
872                    .map(|e| {
873                        create_window_expr(
874                            e,
875                            logical_schema,
876                            session_state.execution_props(),
877                        )
878                    })
879                    .collect::<Result<Vec<_>>>()?;
880
881                let can_repartition = session_state.config().target_partitions() > 1
882                    && session_state.config().repartition_window_functions();
883
884                let uses_bounded_memory =
885                    window_expr.iter().all(|e| e.uses_bounded_memory());
886                // If all window expressions can run with bounded memory,
887                // choose the bounded window variant:
888                if uses_bounded_memory {
889                    Arc::new(BoundedWindowAggExec::try_new(
890                        window_expr,
891                        input_exec,
892                        InputOrderMode::Sorted,
893                        can_repartition,
894                    )?)
895                } else {
896                    Arc::new(WindowAggExec::try_new(
897                        window_expr,
898                        input_exec,
899                        can_repartition,
900                    )?)
901                }
902            }
903            LogicalPlan::Aggregate(Aggregate {
904                input,
905                group_expr,
906                aggr_expr,
907                ..
908            }) => {
909                let options = session_state.config().options();
910                // Initially need to perform the aggregate and then merge the partitions
911                let input_exec = children.one()?;
912                let physical_input_schema = input_exec.schema();
913                let logical_input_schema = input.as_ref().schema();
914                let physical_input_schema_from_logical = logical_input_schema.inner();
915
916                if !options.execution.skip_physical_aggregate_schema_check
917                    && !schema_satisfied_by(
918                        physical_input_schema_from_logical,
919                        &physical_input_schema,
920                    )
921                {
922                    let mut differences = Vec::new();
923
924                    if physical_input_schema.metadata()
925                        != physical_input_schema_from_logical.metadata()
926                    {
927                        differences.push(format!(
928                            "schema metadata differs: (physical) {:?} vs (logical) {:?}",
929                            physical_input_schema.metadata(),
930                            physical_input_schema_from_logical.metadata()
931                        ));
932                    }
933
934                    if physical_input_schema.fields().len()
935                        != physical_input_schema_from_logical.fields().len()
936                    {
937                        differences.push(format!(
938                            "Different number of fields: (physical) {} vs (logical) {}",
939                            physical_input_schema.fields().len(),
940                            physical_input_schema_from_logical.fields().len()
941                        ));
942                    }
943                    for (i, (physical_field, logical_field)) in physical_input_schema
944                        .fields()
945                        .iter()
946                        .zip(physical_input_schema_from_logical.fields())
947                        .enumerate()
948                    {
949                        if physical_field.name() != logical_field.name() {
950                            differences.push(format!(
951                                "field name at index {}: (physical) {} vs (logical) {}",
952                                i,
953                                physical_field.name(),
954                                logical_field.name()
955                            ));
956                        }
957                        if physical_field.data_type() != logical_field.data_type() {
958                            differences.push(format!("field data type at index {} [{}]: (physical) {} vs (logical) {}", i, physical_field.name(), physical_field.data_type(), logical_field.data_type()));
959                        }
960                        if physical_field.is_nullable() && !logical_field.is_nullable() {
961                            differences.push(format!("field nullability at index {} [{}]: (physical) {} vs (logical) {}", i, physical_field.name(), physical_field.is_nullable(), logical_field.is_nullable()));
962                        }
963                        if physical_field.metadata() != logical_field.metadata() {
964                            differences.push(format!(
965                                "field metadata at index {} [{}]: (physical) {:?} vs (logical) {:?}",
966                                i,
967                                physical_field.name(),
968                                physical_field.metadata(),
969                                logical_field.metadata()
970                            ));
971                        }
972                    }
973                    return internal_err!(
974                        "Physical input schema should be the same as the one converted from logical input schema. Differences: {}",
975                        differences.iter().map(|s| format!("\n\t- {s}")).join("")
976                    );
977                }
978
979                let groups = self.create_grouping_physical_expr(
980                    group_expr,
981                    logical_input_schema,
982                    &physical_input_schema,
983                    session_state,
984                )?;
985
986                let agg_filter = aggr_expr
987                    .iter()
988                    .map(|e| {
989                        create_aggregate_expr_and_maybe_filter(
990                            e,
991                            logical_input_schema,
992                            &physical_input_schema,
993                            session_state.execution_props(),
994                        )
995                    })
996                    .collect::<Result<Vec<_>>>()?;
997
998                let (mut aggregates, filters, _order_bys): (Vec<_>, Vec<_>, Vec<_>) =
999                    multiunzip(agg_filter);
1000
1001                let mut async_exprs = Vec::new();
1002                let num_input_columns = physical_input_schema.fields().len();
1003
1004                for agg_func in &mut aggregates {
1005                    match self.try_plan_async_exprs(
1006                        num_input_columns,
1007                        PlannedExprResult::Expr(agg_func.expressions()),
1008                        physical_input_schema.as_ref(),
1009                    )? {
1010                        PlanAsyncExpr::Async(
1011                            async_map,
1012                            PlannedExprResult::Expr(physical_exprs),
1013                        ) => {
1014                            async_exprs.extend(async_map.async_exprs);
1015
1016                            if let Some(new_agg_func) = agg_func.with_new_expressions(
1017                                physical_exprs,
1018                                agg_func
1019                                    .order_bys()
1020                                    .iter()
1021                                    .cloned()
1022                                    .map(|x| x.expr)
1023                                    .collect(),
1024                            ) {
1025                                *agg_func = Arc::new(new_agg_func);
1026                            } else {
1027                                return internal_err!("Failed to plan async expression");
1028                            }
1029                        }
1030                        PlanAsyncExpr::Sync(PlannedExprResult::Expr(_)) => {
1031                            // Do nothing
1032                        }
1033                        _ => {
1034                            return internal_err!(
1035                                "Unexpected result from try_plan_async_exprs"
1036                            );
1037                        }
1038                    }
1039                }
1040                let input_exec = if !async_exprs.is_empty() {
1041                    Arc::new(AsyncFuncExec::try_new(async_exprs, input_exec)?)
1042                } else {
1043                    input_exec
1044                };
1045
1046                let initial_aggr = Arc::new(AggregateExec::try_new(
1047                    AggregateMode::Partial,
1048                    groups.clone(),
1049                    aggregates,
1050                    filters.clone(),
1051                    input_exec,
1052                    Arc::clone(&physical_input_schema),
1053                )?);
1054
1055                let can_repartition = !groups.is_empty()
1056                    && session_state.config().target_partitions() > 1
1057                    && session_state.config().repartition_aggregations();
1058
1059                // Some aggregators may be modified during initialization for
1060                // optimization purposes. For example, a FIRST_VALUE may turn
1061                // into a LAST_VALUE with the reverse ordering requirement.
1062                // To reflect such changes to subsequent stages, use the updated
1063                // `AggregateFunctionExpr`/`PhysicalSortExpr` objects.
1064                let updated_aggregates = initial_aggr.aggr_expr().to_vec();
1065
1066                let next_partition_mode = if can_repartition {
1067                    // construct a second aggregation with 'AggregateMode::FinalPartitioned'
1068                    AggregateMode::FinalPartitioned
1069                } else {
1070                    // construct a second aggregation, keeping the final column name equal to the
1071                    // first aggregation and the expressions corresponding to the respective aggregate
1072                    AggregateMode::Final
1073                };
1074
1075                let final_grouping_set = initial_aggr.group_expr().as_final();
1076
1077                Arc::new(AggregateExec::try_new(
1078                    next_partition_mode,
1079                    final_grouping_set,
1080                    updated_aggregates,
1081                    filters,
1082                    initial_aggr,
1083                    Arc::clone(&physical_input_schema),
1084                )?)
1085            }
1086            LogicalPlan::Projection(Projection { input, expr, .. }) => self
1087                .create_project_physical_exec(
1088                    session_state,
1089                    children.one()?,
1090                    input,
1091                    expr,
1092                )?,
1093            LogicalPlan::Filter(Filter {
1094                predicate, input, ..
1095            }) => {
1096                let physical_input = children.one()?;
1097                let input_dfschema = input.schema();
1098
1099                let runtime_expr =
1100                    self.create_physical_expr(predicate, input_dfschema, session_state)?;
1101
1102                let input_schema = input.schema();
1103                let filter = match self.try_plan_async_exprs(
1104                    input_schema.fields().len(),
1105                    PlannedExprResult::Expr(vec![runtime_expr]),
1106                    input_schema.as_arrow(),
1107                )? {
1108                    PlanAsyncExpr::Sync(PlannedExprResult::Expr(runtime_expr)) => {
1109                        FilterExecBuilder::new(
1110                            Arc::clone(&runtime_expr[0]),
1111                            physical_input,
1112                        )
1113                        .with_batch_size(session_state.config().batch_size())
1114                        .build()?
1115                    }
1116                    PlanAsyncExpr::Async(
1117                        async_map,
1118                        PlannedExprResult::Expr(runtime_expr),
1119                    ) => {
1120                        let async_exec = AsyncFuncExec::try_new(
1121                            async_map.async_exprs,
1122                            physical_input,
1123                        )?;
1124                        FilterExecBuilder::new(
1125                            Arc::clone(&runtime_expr[0]),
1126                            Arc::new(async_exec),
1127                        )
1128                        // project the output columns excluding the async functions
1129                        // The async functions are always appended to the end of the schema.
1130                        .apply_projection(Some(
1131                            (0..input.schema().fields().len()).collect::<Vec<_>>(),
1132                        ))?
1133                        .with_batch_size(session_state.config().batch_size())
1134                        .build()?
1135                    }
1136                    _ => {
1137                        return internal_err!(
1138                            "Unexpected result from try_plan_async_exprs"
1139                        );
1140                    }
1141                };
1142
1143                let selectivity = session_state
1144                    .config()
1145                    .options()
1146                    .optimizer
1147                    .default_filter_selectivity;
1148                Arc::new(filter.with_default_selectivity(selectivity)?)
1149            }
1150            LogicalPlan::Repartition(Repartition {
1151                input,
1152                partitioning_scheme,
1153            }) => {
1154                let physical_input = children.one()?;
1155                let input_dfschema = input.as_ref().schema();
1156                let physical_partitioning = match partitioning_scheme {
1157                    LogicalPartitioning::RoundRobinBatch(n) => {
1158                        Partitioning::RoundRobinBatch(*n)
1159                    }
1160                    LogicalPartitioning::Hash(expr, n) => {
1161                        let runtime_expr = expr
1162                            .iter()
1163                            .map(|e| {
1164                                self.create_physical_expr(
1165                                    e,
1166                                    input_dfschema,
1167                                    session_state,
1168                                )
1169                            })
1170                            .collect::<Result<Vec<_>>>()?;
1171                        Partitioning::Hash(runtime_expr, *n)
1172                    }
1173                    LogicalPartitioning::DistributeBy(_) => {
1174                        return not_impl_err!(
1175                            "Physical plan does not support DistributeBy partitioning"
1176                        );
1177                    }
1178                };
1179                Arc::new(RepartitionExec::try_new(
1180                    physical_input,
1181                    physical_partitioning,
1182                )?)
1183            }
1184            LogicalPlan::Sort(Sort {
1185                expr, input, fetch, ..
1186            }) => {
1187                let physical_input = children.one()?;
1188                let input_dfschema = input.as_ref().schema();
1189                let sort_exprs = create_physical_sort_exprs(
1190                    expr,
1191                    input_dfschema,
1192                    session_state.execution_props(),
1193                )?;
1194                let Some(ordering) = LexOrdering::new(sort_exprs) else {
1195                    return internal_err!(
1196                        "SortExec requires at least one sort expression"
1197                    );
1198                };
1199                let new_sort = SortExec::new(ordering, physical_input).with_fetch(*fetch);
1200                Arc::new(new_sort)
1201            }
1202            LogicalPlan::Subquery(_) => todo!(),
1203            LogicalPlan::SubqueryAlias(_) => children.one()?,
1204            LogicalPlan::Limit(limit) => {
1205                let input = children.one()?;
1206                let SkipType::Literal(skip) = limit.get_skip_type()? else {
1207                    return not_impl_err!(
1208                        "Unsupported OFFSET expression: {:?}",
1209                        limit.skip
1210                    );
1211                };
1212                let FetchType::Literal(fetch) = limit.get_fetch_type()? else {
1213                    return not_impl_err!(
1214                        "Unsupported LIMIT expression: {:?}",
1215                        limit.fetch
1216                    );
1217                };
1218
1219                // GlobalLimitExec requires a single partition for input
1220                let input = if input.output_partitioning().partition_count() == 1 {
1221                    input
1222                } else {
1223                    // Apply a LocalLimitExec to each partition. The optimizer will also insert
1224                    // a CoalescePartitionsExec between the GlobalLimitExec and LocalLimitExec
1225                    if let Some(fetch) = fetch {
1226                        Arc::new(LocalLimitExec::new(input, fetch + skip))
1227                    } else {
1228                        input
1229                    }
1230                };
1231
1232                Arc::new(GlobalLimitExec::new(input, skip, fetch))
1233            }
1234            LogicalPlan::Unnest(Unnest {
1235                list_type_columns,
1236                struct_type_columns,
1237                schema,
1238                options,
1239                ..
1240            }) => {
1241                let input = children.one()?;
1242                let schema = Arc::clone(schema.inner());
1243                let list_column_indices = list_type_columns
1244                    .iter()
1245                    .map(|(index, unnesting)| ListUnnest {
1246                        index_in_input_schema: *index,
1247                        depth: unnesting.depth,
1248                    })
1249                    .collect();
1250                Arc::new(UnnestExec::new(
1251                    input,
1252                    list_column_indices,
1253                    struct_type_columns.clone(),
1254                    schema,
1255                    options.clone(),
1256                )?)
1257            }
1258
1259            // 2 Children
1260            LogicalPlan::Join(Join {
1261                left: original_left,
1262                right: original_right,
1263                on: keys,
1264                filter,
1265                join_type,
1266                null_equality,
1267                null_aware,
1268                schema: join_schema,
1269                ..
1270            }) => {
1271                let [physical_left, physical_right] = children.two()?;
1272
1273                // If join has expression equijoin keys, add physical projection.
1274                let has_expr_join_key = keys.iter().any(|(l, r)| {
1275                    !(matches!(l, Expr::Column(_)) && matches!(r, Expr::Column(_)))
1276                });
1277                let (new_logical, physical_left, physical_right) = if has_expr_join_key {
1278                    // TODO: Can we extract this transformation to somewhere before physical plan
1279                    //       creation?
1280                    let (left_keys, right_keys): (Vec<_>, Vec<_>) =
1281                        keys.iter().cloned().unzip();
1282
1283                    let (left, left_col_keys, left_projected) =
1284                        wrap_projection_for_join_if_necessary(
1285                            &left_keys,
1286                            original_left.as_ref().clone(),
1287                        )?;
1288                    let (right, right_col_keys, right_projected) =
1289                        wrap_projection_for_join_if_necessary(
1290                            &right_keys,
1291                            original_right.as_ref().clone(),
1292                        )?;
1293                    let column_on = (left_col_keys, right_col_keys);
1294
1295                    let left = Arc::new(left);
1296                    let right = Arc::new(right);
1297                    let (new_join, requalified) = Join::try_new_with_project_input(
1298                        node,
1299                        Arc::clone(&left),
1300                        Arc::clone(&right),
1301                        column_on,
1302                    )?;
1303
1304                    let new_join = LogicalPlan::Join(new_join);
1305
1306                    // If inputs were projected then create ExecutionPlan for these new
1307                    // LogicalPlan nodes.
1308                    let physical_left = match (left_projected, left.as_ref()) {
1309                        // If left_projected is true we are guaranteed that left is a Projection
1310                        (
1311                            true,
1312                            LogicalPlan::Projection(Projection { input, expr, .. }),
1313                        ) => self.create_project_physical_exec(
1314                            session_state,
1315                            physical_left,
1316                            input,
1317                            expr,
1318                        )?,
1319                        _ => physical_left,
1320                    };
1321                    let physical_right = match (right_projected, right.as_ref()) {
1322                        // If right_projected is true we are guaranteed that right is a Projection
1323                        (
1324                            true,
1325                            LogicalPlan::Projection(Projection { input, expr, .. }),
1326                        ) => self.create_project_physical_exec(
1327                            session_state,
1328                            physical_right,
1329                            input,
1330                            expr,
1331                        )?,
1332                        _ => physical_right,
1333                    };
1334
1335                    // Remove temporary projected columns
1336                    if left_projected || right_projected {
1337                        // Re-qualify the join schema only if the inputs were previously requalified in
1338                        // `try_new_with_project_input`. This ensures that when building the Projection
1339                        // it can correctly resolve field nullability and data types
1340                        // by disambiguating fields from the left and right sides of the join.
1341                        let qualified_join_schema = if requalified {
1342                            Arc::new(qualify_join_schema_sides(
1343                                join_schema,
1344                                original_left,
1345                                original_right,
1346                            )?)
1347                        } else {
1348                            Arc::clone(join_schema)
1349                        };
1350
1351                        let final_join_result = qualified_join_schema
1352                            .iter()
1353                            .map(Expr::from)
1354                            .collect::<Vec<_>>();
1355                        let projection = LogicalPlan::Projection(Projection::try_new(
1356                            final_join_result,
1357                            Arc::new(new_join),
1358                        )?);
1359                        // LogicalPlan mutated
1360                        (Cow::Owned(projection), physical_left, physical_right)
1361                    } else {
1362                        // LogicalPlan mutated
1363                        (Cow::Owned(new_join), physical_left, physical_right)
1364                    }
1365                } else {
1366                    // LogicalPlan unchanged
1367                    (Cow::Borrowed(node), physical_left, physical_right)
1368                };
1369
1370                // Retrieving new left/right and join keys (in case plan was mutated above)
1371                let (left, right, keys, new_project) = match new_logical.as_ref() {
1372                    LogicalPlan::Projection(Projection { input, expr, .. }) => {
1373                        if let LogicalPlan::Join(Join {
1374                            left, right, on, ..
1375                        }) = input.as_ref()
1376                        {
1377                            (left, right, on, Some((input, expr)))
1378                        } else {
1379                            unreachable!()
1380                        }
1381                    }
1382                    LogicalPlan::Join(Join {
1383                        left, right, on, ..
1384                    }) => (left, right, on, None),
1385                    // Should either be the original Join, or Join with a Projection on top
1386                    _ => unreachable!(),
1387                };
1388
1389                // All equi-join keys are columns now, create physical join plan
1390                let left_df_schema = left.schema();
1391                let right_df_schema = right.schema();
1392                let execution_props = session_state.execution_props();
1393                let join_on = keys
1394                    .iter()
1395                    .map(|(l, r)| {
1396                        let l = create_physical_expr(l, left_df_schema, execution_props)?;
1397                        let r =
1398                            create_physical_expr(r, right_df_schema, execution_props)?;
1399                        Ok((l, r))
1400                    })
1401                    .collect::<Result<join_utils::JoinOn>>()?;
1402
1403                // TODO: `num_range_filters` can be used later on for ASOF joins (`num_range_filters > 1`)
1404                let mut num_range_filters = 0;
1405                let mut range_filters: Vec<Expr> = Vec::new();
1406                let mut total_filters = 0;
1407
1408                let join_filter = match filter {
1409                    Some(expr) => {
1410                        let split_expr = split_conjunction(expr);
1411                        for expr in split_expr.iter() {
1412                            match *expr {
1413                                Expr::BinaryExpr(BinaryExpr {
1414                                    left: _,
1415                                    right: _,
1416                                    op,
1417                                }) => {
1418                                    if matches!(
1419                                        op,
1420                                        Operator::Lt
1421                                            | Operator::LtEq
1422                                            | Operator::Gt
1423                                            | Operator::GtEq
1424                                    ) {
1425                                        range_filters.push((**expr).clone());
1426                                        num_range_filters += 1;
1427                                    }
1428                                    total_filters += 1;
1429                                }
1430                                // TODO: Want to deal with `Expr::Between` for IEJoins, it counts as two range predicates
1431                                // which is why it is not dealt with in PWMJ
1432                                // Expr::Between(_) => {},
1433                                _ => {
1434                                    total_filters += 1;
1435                                }
1436                            }
1437                        }
1438
1439                        // Extract columns from filter expression and saved in a HashSet
1440                        let cols = expr.column_refs();
1441
1442                        // Collect left & right field indices, the field indices are sorted in ascending order
1443                        let left_field_indices = cols
1444                            .iter()
1445                            .filter_map(|c| left_df_schema.index_of_column(c).ok())
1446                            .sorted()
1447                            .collect::<Vec<_>>();
1448                        let right_field_indices = cols
1449                            .iter()
1450                            .filter_map(|c| right_df_schema.index_of_column(c).ok())
1451                            .sorted()
1452                            .collect::<Vec<_>>();
1453
1454                        // Collect DFFields and Fields required for intermediate schemas
1455                        let (filter_df_fields, filter_fields): (Vec<_>, Vec<_>) =
1456                            left_field_indices
1457                                .clone()
1458                                .into_iter()
1459                                .map(|i| {
1460                                    (
1461                                        left_df_schema.qualified_field(i),
1462                                        physical_left.schema().field(i).clone(),
1463                                    )
1464                                })
1465                                .chain(right_field_indices.clone().into_iter().map(|i| {
1466                                    (
1467                                        right_df_schema.qualified_field(i),
1468                                        physical_right.schema().field(i).clone(),
1469                                    )
1470                                }))
1471                                .unzip();
1472                        let filter_df_fields = filter_df_fields
1473                            .into_iter()
1474                            .map(|(qualifier, field)| {
1475                                (qualifier.cloned(), Arc::clone(field))
1476                            })
1477                            .collect();
1478
1479                        let metadata: HashMap<_, _> = left_df_schema
1480                            .metadata()
1481                            .clone()
1482                            .into_iter()
1483                            .chain(right_df_schema.metadata().clone())
1484                            .collect();
1485
1486                        // Construct intermediate schemas used for filtering data and
1487                        // convert logical expression to physical according to filter schema
1488                        let filter_df_schema = DFSchema::new_with_metadata(
1489                            filter_df_fields,
1490                            metadata.clone(),
1491                        )?;
1492                        let filter_schema =
1493                            Schema::new_with_metadata(filter_fields, metadata);
1494
1495                        let filter_expr = create_physical_expr(
1496                            expr,
1497                            &filter_df_schema,
1498                            session_state.execution_props(),
1499                        )?;
1500                        let column_indices = join_utils::JoinFilter::build_column_indices(
1501                            left_field_indices,
1502                            right_field_indices,
1503                        );
1504
1505                        Some(join_utils::JoinFilter::new(
1506                            filter_expr,
1507                            column_indices,
1508                            Arc::new(filter_schema),
1509                        ))
1510                    }
1511                    _ => None,
1512                };
1513
1514                let prefer_hash_join =
1515                    session_state.config_options().optimizer.prefer_hash_join;
1516
1517                // TODO: Allow PWMJ to deal with residual equijoin conditions
1518                let join: Arc<dyn ExecutionPlan> = if join_on.is_empty() {
1519                    if join_filter.is_none() && *join_type == JoinType::Inner {
1520                        // cross join if there is no join conditions and no join filter set
1521                        Arc::new(CrossJoinExec::new(physical_left, physical_right))
1522                    } else if num_range_filters == 1
1523                        && total_filters == 1
1524                        && !matches!(
1525                            join_type,
1526                            JoinType::LeftSemi
1527                                | JoinType::RightSemi
1528                                | JoinType::LeftAnti
1529                                | JoinType::RightAnti
1530                                | JoinType::LeftMark
1531                                | JoinType::RightMark
1532                        )
1533                        && session_state
1534                            .config_options()
1535                            .optimizer
1536                            .enable_piecewise_merge_join
1537                    {
1538                        let Expr::BinaryExpr(be) = &range_filters[0] else {
1539                            return plan_err!(
1540                                "Unsupported expression for PWMJ: Expected `Expr::BinaryExpr`"
1541                            );
1542                        };
1543
1544                        let mut op = be.op;
1545                        if !matches!(
1546                            op,
1547                            Operator::Lt | Operator::LtEq | Operator::Gt | Operator::GtEq
1548                        ) {
1549                            return plan_err!(
1550                                "Unsupported operator for PWMJ: {:?}. Expected one of <, <=, >, >=",
1551                                op
1552                            );
1553                        }
1554
1555                        fn reverse_ineq(op: Operator) -> Operator {
1556                            match op {
1557                                Operator::Lt => Operator::Gt,
1558                                Operator::LtEq => Operator::GtEq,
1559                                Operator::Gt => Operator::Lt,
1560                                Operator::GtEq => Operator::LtEq,
1561                                _ => op,
1562                            }
1563                        }
1564
1565                        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1566                        enum Side {
1567                            Left,
1568                            Right,
1569                            Both,
1570                        }
1571
1572                        let side_of = |e: &Expr| -> Result<Side> {
1573                            let cols = e.column_refs();
1574                            let any_left = cols
1575                                .iter()
1576                                .any(|c| left_df_schema.index_of_column(c).is_ok());
1577                            let any_right = cols
1578                                .iter()
1579                                .any(|c| right_df_schema.index_of_column(c).is_ok());
1580
1581                            Ok(match (any_left, any_right) {
1582                                (true, false) => Side::Left,
1583                                (false, true) => Side::Right,
1584                                (true, true) => Side::Both,
1585                                _ => unreachable!(),
1586                            })
1587                        };
1588
1589                        let mut lhs_logical = &be.left;
1590                        let mut rhs_logical = &be.right;
1591
1592                        let left_side = side_of(lhs_logical)?;
1593                        let right_side = side_of(rhs_logical)?;
1594                        if left_side == Side::Both || right_side == Side::Both {
1595                            return Ok(Arc::new(NestedLoopJoinExec::try_new(
1596                                physical_left,
1597                                physical_right,
1598                                join_filter,
1599                                join_type,
1600                                None,
1601                            )?));
1602                        }
1603
1604                        if left_side == Side::Right && right_side == Side::Left {
1605                            std::mem::swap(&mut lhs_logical, &mut rhs_logical);
1606                            op = reverse_ineq(op);
1607                        } else if !(left_side == Side::Left && right_side == Side::Right)
1608                        {
1609                            return plan_err!(
1610                                "Unsupported operator for PWMJ: {:?}. Expected one of <, <=, >, >=",
1611                                op
1612                            );
1613                        }
1614
1615                        let on_left = create_physical_expr(
1616                            lhs_logical,
1617                            left_df_schema,
1618                            session_state.execution_props(),
1619                        )?;
1620                        let on_right = create_physical_expr(
1621                            rhs_logical,
1622                            right_df_schema,
1623                            session_state.execution_props(),
1624                        )?;
1625
1626                        Arc::new(PiecewiseMergeJoinExec::try_new(
1627                            physical_left,
1628                            physical_right,
1629                            (on_left, on_right),
1630                            op,
1631                            *join_type,
1632                            session_state.config().target_partitions(),
1633                        )?)
1634                    } else {
1635                        // there is no equal join condition, use the nested loop join
1636                        Arc::new(NestedLoopJoinExec::try_new(
1637                            physical_left,
1638                            physical_right,
1639                            join_filter,
1640                            join_type,
1641                            None,
1642                        )?)
1643                    }
1644                } else if session_state.config().target_partitions() > 1
1645                    && session_state.config().repartition_joins()
1646                    && !prefer_hash_join
1647                {
1648                    // Use SortMergeJoin if hash join is not preferred
1649                    let join_on_len = join_on.len();
1650                    Arc::new(SortMergeJoinExec::try_new(
1651                        physical_left,
1652                        physical_right,
1653                        join_on,
1654                        join_filter,
1655                        *join_type,
1656                        vec![SortOptions::default(); join_on_len],
1657                        *null_equality,
1658                    )?)
1659                } else if session_state.config().target_partitions() > 1
1660                    && session_state.config().repartition_joins()
1661                    && prefer_hash_join
1662                    && !*null_aware
1663                // Null-aware joins must use CollectLeft
1664                {
1665                    Arc::new(HashJoinExec::try_new(
1666                        physical_left,
1667                        physical_right,
1668                        join_on,
1669                        join_filter,
1670                        join_type,
1671                        None,
1672                        PartitionMode::Auto,
1673                        *null_equality,
1674                        *null_aware,
1675                    )?)
1676                } else {
1677                    Arc::new(HashJoinExec::try_new(
1678                        physical_left,
1679                        physical_right,
1680                        join_on,
1681                        join_filter,
1682                        join_type,
1683                        None,
1684                        PartitionMode::CollectLeft,
1685                        *null_equality,
1686                        *null_aware,
1687                    )?)
1688                };
1689
1690                // If plan was mutated previously then need to create the ExecutionPlan
1691                // for the new Projection that was applied on top.
1692                if let Some((input, expr)) = new_project {
1693                    self.create_project_physical_exec(session_state, join, input, expr)?
1694                } else {
1695                    join
1696                }
1697            }
1698            LogicalPlan::RecursiveQuery(RecursiveQuery {
1699                name, is_distinct, ..
1700            }) => {
1701                let [static_term, recursive_term] = children.two()?;
1702                Arc::new(RecursiveQueryExec::try_new(
1703                    name.clone(),
1704                    static_term,
1705                    recursive_term,
1706                    *is_distinct,
1707                )?)
1708            }
1709
1710            // N Children
1711            LogicalPlan::Union(_) => UnionExec::try_new(children.vec())?,
1712            LogicalPlan::Extension(Extension { node }) => {
1713                let mut maybe_plan = None;
1714                let children = children.vec();
1715                for planner in &self.extension_planners {
1716                    if maybe_plan.is_some() {
1717                        break;
1718                    }
1719
1720                    let logical_input = node.inputs();
1721                    maybe_plan = planner
1722                        .plan_extension(
1723                            self,
1724                            node.as_ref(),
1725                            &logical_input,
1726                            &children,
1727                            session_state,
1728                        )
1729                        .await?;
1730                }
1731
1732                let plan = match maybe_plan {
1733                    Some(v) => Ok(v),
1734                    _ => plan_err!(
1735                        "No installed planner was able to convert the custom node to an execution plan: {:?}",
1736                        node
1737                    ),
1738                }?;
1739
1740                let context = format!("Extension planner for {node:?}");
1741                self.ensure_schema_matches(node.schema(), &plan, &context)?;
1742                plan
1743            }
1744
1745            // Other
1746            LogicalPlan::Statement(statement) => {
1747                // DataFusion is a read-only query engine, but also a library, so consumers may implement this
1748                let name = statement.name();
1749                return not_impl_err!("Unsupported logical plan: Statement({name})");
1750            }
1751            LogicalPlan::Dml(dml) => {
1752                // DataFusion is a read-only query engine, but also a library, so consumers may implement this
1753                return not_impl_err!("Unsupported logical plan: Dml({0})", dml.op);
1754            }
1755            LogicalPlan::Ddl(ddl) => {
1756                // There is no default plan for DDl statements --
1757                // it must be handled at a higher level (so that
1758                // the appropriate table can be registered with
1759                // the context)
1760                let name = ddl.name();
1761                return not_impl_err!("Unsupported logical plan: {name}");
1762            }
1763            LogicalPlan::Explain(_) => {
1764                return internal_err!(
1765                    "Unsupported logical plan: Explain must be root of the plan"
1766                );
1767            }
1768            LogicalPlan::Distinct(_) => {
1769                return internal_err!(
1770                    "Unsupported logical plan: Distinct should be replaced to Aggregate"
1771                );
1772            }
1773            LogicalPlan::Analyze(_) => {
1774                return internal_err!(
1775                    "Unsupported logical plan: Analyze must be root of the plan"
1776                );
1777            }
1778        };
1779        Ok(exec_node)
1780    }
1781
1782    fn create_grouping_physical_expr(
1783        &self,
1784        group_expr: &[Expr],
1785        input_dfschema: &DFSchema,
1786        input_schema: &Schema,
1787        session_state: &SessionState,
1788    ) -> Result<PhysicalGroupBy> {
1789        if group_expr.len() == 1 {
1790            match &group_expr[0] {
1791                Expr::GroupingSet(GroupingSet::GroupingSets(grouping_sets)) => {
1792                    merge_grouping_set_physical_expr(
1793                        grouping_sets,
1794                        input_dfschema,
1795                        input_schema,
1796                        session_state,
1797                    )
1798                }
1799                Expr::GroupingSet(GroupingSet::Cube(exprs)) => create_cube_physical_expr(
1800                    exprs,
1801                    input_dfschema,
1802                    input_schema,
1803                    session_state,
1804                ),
1805                Expr::GroupingSet(GroupingSet::Rollup(exprs)) => {
1806                    create_rollup_physical_expr(
1807                        exprs,
1808                        input_dfschema,
1809                        input_schema,
1810                        session_state,
1811                    )
1812                }
1813                expr => Ok(PhysicalGroupBy::new_single(vec![tuple_err((
1814                    self.create_physical_expr(expr, input_dfschema, session_state),
1815                    physical_name(expr),
1816                ))?])),
1817            }
1818        } else if group_expr.is_empty() {
1819            // No GROUP BY clause - create empty PhysicalGroupBy
1820            // no expressions, no null expressions and no grouping expressions
1821            Ok(PhysicalGroupBy::new(vec![], vec![], vec![], false))
1822        } else {
1823            Ok(PhysicalGroupBy::new_single(
1824                group_expr
1825                    .iter()
1826                    .map(|e| {
1827                        tuple_err((
1828                            self.create_physical_expr(e, input_dfschema, session_state),
1829                            physical_name(e),
1830                        ))
1831                    })
1832                    .collect::<Result<Vec<_>>>()?,
1833            ))
1834        }
1835    }
1836}
1837
1838/// Expand and align a GROUPING SET expression.
1839/// (see <https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-GROUPING-SETS>)
1840///
1841/// This will take a list of grouping sets and ensure that each group is
1842/// properly aligned for the physical execution plan. We do this by
1843/// identifying all unique expression in each group and conforming each
1844/// group to the same set of expression types and ordering.
1845/// For example, if we have something like `GROUPING SETS ((a,b,c),(a),(b),(b,c))`
1846/// we would expand this to `GROUPING SETS ((a,b,c),(a,NULL,NULL),(NULL,b,NULL),(NULL,b,c))
1847/// (see <https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-GROUPING-SETS>)
1848fn merge_grouping_set_physical_expr(
1849    grouping_sets: &[Vec<Expr>],
1850    input_dfschema: &DFSchema,
1851    input_schema: &Schema,
1852    session_state: &SessionState,
1853) -> Result<PhysicalGroupBy> {
1854    let num_groups = grouping_sets.len();
1855    let mut all_exprs: Vec<Expr> = vec![];
1856    let mut grouping_set_expr: Vec<(Arc<dyn PhysicalExpr>, String)> = vec![];
1857    let mut null_exprs: Vec<(Arc<dyn PhysicalExpr>, String)> = vec![];
1858
1859    for expr in grouping_sets.iter().flatten() {
1860        if !all_exprs.contains(expr) {
1861            all_exprs.push(expr.clone());
1862
1863            grouping_set_expr.push(get_physical_expr_pair(
1864                expr,
1865                input_dfschema,
1866                session_state,
1867            )?);
1868
1869            null_exprs.push(get_null_physical_expr_pair(
1870                expr,
1871                input_dfschema,
1872                input_schema,
1873                session_state,
1874            )?);
1875        }
1876    }
1877
1878    let mut merged_sets: Vec<Vec<bool>> = Vec::with_capacity(num_groups);
1879
1880    for expr_group in grouping_sets.iter() {
1881        let group: Vec<bool> = all_exprs
1882            .iter()
1883            .map(|expr| !expr_group.contains(expr))
1884            .collect();
1885
1886        merged_sets.push(group)
1887    }
1888
1889    Ok(PhysicalGroupBy::new(
1890        grouping_set_expr,
1891        null_exprs,
1892        merged_sets,
1893        true,
1894    ))
1895}
1896
1897/// Expand and align a CUBE expression. This is a special case of GROUPING SETS
1898/// (see <https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-GROUPING-SETS>)
1899fn create_cube_physical_expr(
1900    exprs: &[Expr],
1901    input_dfschema: &DFSchema,
1902    input_schema: &Schema,
1903    session_state: &SessionState,
1904) -> Result<PhysicalGroupBy> {
1905    let num_of_exprs = exprs.len();
1906    let num_groups = num_of_exprs * num_of_exprs;
1907
1908    let mut null_exprs: Vec<(Arc<dyn PhysicalExpr>, String)> =
1909        Vec::with_capacity(num_of_exprs);
1910    let mut all_exprs: Vec<(Arc<dyn PhysicalExpr>, String)> =
1911        Vec::with_capacity(num_of_exprs);
1912
1913    for expr in exprs {
1914        null_exprs.push(get_null_physical_expr_pair(
1915            expr,
1916            input_dfschema,
1917            input_schema,
1918            session_state,
1919        )?);
1920
1921        all_exprs.push(get_physical_expr_pair(expr, input_dfschema, session_state)?)
1922    }
1923
1924    let mut groups: Vec<Vec<bool>> = Vec::with_capacity(num_groups);
1925
1926    groups.push(vec![false; num_of_exprs]);
1927
1928    for null_count in 1..=num_of_exprs {
1929        for null_idx in (0..num_of_exprs).combinations(null_count) {
1930            let mut next_group: Vec<bool> = vec![false; num_of_exprs];
1931            null_idx.into_iter().for_each(|i| next_group[i] = true);
1932            groups.push(next_group);
1933        }
1934    }
1935
1936    Ok(PhysicalGroupBy::new(all_exprs, null_exprs, groups, true))
1937}
1938
1939/// Expand and align a ROLLUP expression. This is a special case of GROUPING SETS
1940/// (see <https://www.postgresql.org/docs/current/queries-table-expressions.html#QUERIES-GROUPING-SETS>)
1941fn create_rollup_physical_expr(
1942    exprs: &[Expr],
1943    input_dfschema: &DFSchema,
1944    input_schema: &Schema,
1945    session_state: &SessionState,
1946) -> Result<PhysicalGroupBy> {
1947    let num_of_exprs = exprs.len();
1948
1949    let mut null_exprs: Vec<(Arc<dyn PhysicalExpr>, String)> =
1950        Vec::with_capacity(num_of_exprs);
1951    let mut all_exprs: Vec<(Arc<dyn PhysicalExpr>, String)> =
1952        Vec::with_capacity(num_of_exprs);
1953
1954    let mut groups: Vec<Vec<bool>> = Vec::with_capacity(num_of_exprs + 1);
1955
1956    for expr in exprs {
1957        null_exprs.push(get_null_physical_expr_pair(
1958            expr,
1959            input_dfschema,
1960            input_schema,
1961            session_state,
1962        )?);
1963
1964        all_exprs.push(get_physical_expr_pair(expr, input_dfschema, session_state)?)
1965    }
1966
1967    for total in 0..=num_of_exprs {
1968        let mut group: Vec<bool> = Vec::with_capacity(num_of_exprs);
1969
1970        for index in 0..num_of_exprs {
1971            if index < total {
1972                group.push(false);
1973            } else {
1974                group.push(true);
1975            }
1976        }
1977
1978        groups.push(group)
1979    }
1980
1981    Ok(PhysicalGroupBy::new(all_exprs, null_exprs, groups, true))
1982}
1983
1984/// For a given logical expr, get a properly typed NULL ScalarValue physical expression
1985fn get_null_physical_expr_pair(
1986    expr: &Expr,
1987    input_dfschema: &DFSchema,
1988    input_schema: &Schema,
1989    session_state: &SessionState,
1990) -> Result<(Arc<dyn PhysicalExpr>, String)> {
1991    let physical_expr =
1992        create_physical_expr(expr, input_dfschema, session_state.execution_props())?;
1993    let physical_name = physical_name(&expr.clone())?;
1994
1995    let data_type = physical_expr.data_type(input_schema)?;
1996    let null_value: ScalarValue = (&data_type).try_into()?;
1997
1998    let null_value = Literal::new(null_value);
1999    Ok((Arc::new(null_value), physical_name))
2000}
2001
2002/// Qualifies the fields in a join schema with "left" and "right" qualifiers
2003/// without mutating the original schema. This function should only be used when
2004/// the join inputs have already been requalified earlier in `try_new_with_project_input`.
2005///
2006/// The purpose is to avoid ambiguity errors later in planning (e.g., in nullability or data type resolution)
2007/// when converting expressions to fields.
2008fn qualify_join_schema_sides(
2009    join_schema: &DFSchema,
2010    left: &LogicalPlan,
2011    right: &LogicalPlan,
2012) -> Result<DFSchema> {
2013    let left_fields = left.schema().fields();
2014    let right_fields = right.schema().fields();
2015    let join_fields = join_schema.fields();
2016
2017    // Validate lengths
2018    assert_eq_or_internal_err!(
2019        join_fields.len(),
2020        left_fields.len() + right_fields.len(),
2021        "Join schema field count must match left and right field count."
2022    );
2023
2024    // Validate field names match
2025    for (i, (field, expected)) in join_fields
2026        .iter()
2027        .zip(left_fields.iter().chain(right_fields.iter()))
2028        .enumerate()
2029    {
2030        assert_eq_or_internal_err!(
2031            field.name(),
2032            expected.name(),
2033            "Field name mismatch at index {}",
2034            i
2035        );
2036    }
2037
2038    // qualify sides
2039    let qualifiers = join_fields
2040        .iter()
2041        .enumerate()
2042        .map(|(i, _)| {
2043            if i < left_fields.len() {
2044                Some(TableReference::Bare {
2045                    table: Arc::from("left"),
2046                })
2047            } else {
2048                Some(TableReference::Bare {
2049                    table: Arc::from("right"),
2050                })
2051            }
2052        })
2053        .collect();
2054
2055    join_schema.with_field_specific_qualified_schema(qualifiers)
2056}
2057
2058fn get_physical_expr_pair(
2059    expr: &Expr,
2060    input_dfschema: &DFSchema,
2061    session_state: &SessionState,
2062) -> Result<(Arc<dyn PhysicalExpr>, String)> {
2063    let physical_expr =
2064        create_physical_expr(expr, input_dfschema, session_state.execution_props())?;
2065    let physical_name = physical_name(expr)?;
2066    Ok((physical_expr, physical_name))
2067}
2068
2069/// Extract filter predicates from a DML input plan (DELETE/UPDATE).
2070///
2071/// Walks the logical plan tree and collects Filter predicates and any filters
2072/// pushed down into TableScan nodes, splitting AND conjunctions into individual expressions.
2073///
2074/// For UPDATE...FROM queries involving multiple tables, this function only extracts predicates
2075/// that reference the target table. Filters from source table scans are excluded to prevent
2076/// incorrect filter semantics.
2077///
2078/// Column qualifiers are stripped so expressions can be evaluated against the TableProvider's
2079/// schema. Deduplication is performed because filters may appear in both Filter nodes and
2080/// TableScan.filters when the optimizer performs partial (Inexact) filter pushdown.
2081///
2082/// # Parameters
2083/// - `input`: The logical plan tree to extract filters from (typically a DELETE or UPDATE plan)
2084/// - `target`: The target table reference to scope filter extraction (prevents multi-table filter leakage)
2085///
2086/// # Returns
2087/// A vector of unqualified filter expressions that can be passed to the TableProvider for execution.
2088/// Returns an empty vector if no applicable filters are found.
2089///
2090fn extract_dml_filters(
2091    input: &Arc<LogicalPlan>,
2092    target: &TableReference,
2093) -> Result<Vec<Expr>> {
2094    let mut filters = Vec::new();
2095    let mut allowed_refs = vec![target.clone()];
2096
2097    // First pass: collect any alias references to the target table
2098    input.apply(|node| {
2099        if let LogicalPlan::SubqueryAlias(alias) = node
2100            // Check if this alias points to the target table
2101            && let LogicalPlan::TableScan(scan) = alias.input.as_ref()
2102            && scan.table_name.resolved_eq(target)
2103        {
2104            allowed_refs.push(TableReference::bare(alias.alias.to_string()));
2105        }
2106        Ok(TreeNodeRecursion::Continue)
2107    })?;
2108
2109    input.apply(|node| {
2110        match node {
2111            LogicalPlan::Filter(filter) => {
2112                // Split AND predicates into individual expressions
2113                for predicate in split_conjunction(&filter.predicate) {
2114                    if predicate_is_on_target_multi(predicate, &allowed_refs)? {
2115                        filters.push(predicate.clone());
2116                    }
2117                }
2118            }
2119            LogicalPlan::TableScan(TableScan {
2120                table_name,
2121                filters: scan_filters,
2122                ..
2123            }) => {
2124                // Only extract filters from the target table scan.
2125                // This prevents incorrect filter extraction in UPDATE...FROM scenarios
2126                // where multiple table scans may have filters.
2127                if table_name.resolved_eq(target) {
2128                    for filter in scan_filters {
2129                        filters.extend(split_conjunction(filter).into_iter().cloned());
2130                    }
2131                }
2132            }
2133            // Plans without filter information
2134            LogicalPlan::EmptyRelation(_)
2135            | LogicalPlan::Values(_)
2136            | LogicalPlan::DescribeTable(_)
2137            | LogicalPlan::Explain(_)
2138            | LogicalPlan::Analyze(_)
2139            | LogicalPlan::Distinct(_)
2140            | LogicalPlan::Extension(_)
2141            | LogicalPlan::Statement(_)
2142            | LogicalPlan::Dml(_)
2143            | LogicalPlan::Ddl(_)
2144            | LogicalPlan::Copy(_)
2145            | LogicalPlan::Unnest(_)
2146            | LogicalPlan::RecursiveQuery(_) => {
2147                // No filters to extract from leaf/meta plans
2148            }
2149            // Plans with inputs (may contain filters in children)
2150            LogicalPlan::Projection(_)
2151            | LogicalPlan::SubqueryAlias(_)
2152            | LogicalPlan::Limit(_)
2153            | LogicalPlan::Sort(_)
2154            | LogicalPlan::Union(_)
2155            | LogicalPlan::Join(_)
2156            | LogicalPlan::Repartition(_)
2157            | LogicalPlan::Aggregate(_)
2158            | LogicalPlan::Window(_)
2159            | LogicalPlan::Subquery(_) => {
2160                // Filter information may appear in child nodes; continue traversal
2161                // to extract filters from Filter/TableScan nodes deeper in the plan
2162            }
2163        }
2164        Ok(TreeNodeRecursion::Continue)
2165    })?;
2166
2167    // Strip qualifiers and deduplicate. This ensures:
2168    // 1. Only target-table predicates are retained from Filter nodes
2169    // 2. Qualifiers stripped for TableProvider compatibility
2170    // 3. Duplicates removed (from Filter nodes + TableScan.filters)
2171    //
2172    // Deduplication is necessary because filters may appear in both Filter nodes
2173    // and TableScan.filters when the optimizer performs partial (Inexact) pushdown.
2174    let mut seen_filters = HashSet::new();
2175    filters
2176        .into_iter()
2177        .try_fold(Vec::new(), |mut deduped, filter| {
2178            let unqualified = strip_column_qualifiers(filter).map_err(|e| {
2179                e.context(format!(
2180                    "Failed to strip column qualifiers for DML filter on table '{target}'"
2181                ))
2182            })?;
2183            if seen_filters.insert(unqualified.clone()) {
2184                deduped.push(unqualified);
2185            }
2186            Ok(deduped)
2187        })
2188}
2189
2190/// Determine whether a predicate references only columns from the target table
2191/// or its aliases.
2192///
2193/// Columns may be qualified with the target table name or any of its aliases.
2194/// Unqualified columns are also accepted as they implicitly belong to the target table.
2195fn predicate_is_on_target_multi(
2196    expr: &Expr,
2197    allowed_refs: &[TableReference],
2198) -> Result<bool> {
2199    let mut columns = HashSet::new();
2200    expr_to_columns(expr, &mut columns)?;
2201
2202    // Short-circuit on first mismatch: returns false if any column references a table not in allowed_refs.
2203    // Columns are accepted if:
2204    // 1. They are unqualified (no relation specified), OR
2205    // 2. Their relation matches one of the allowed table references using resolved equality
2206    Ok(!columns.iter().any(|column| {
2207        column.relation.as_ref().is_some_and(|relation| {
2208            !allowed_refs
2209                .iter()
2210                .any(|allowed| relation.resolved_eq(allowed))
2211        })
2212    }))
2213}
2214
2215/// Strip table qualifiers from column references in an expression.
2216/// This is needed because DML filter expressions contain qualified column names
2217/// (e.g., "table.column") but the TableProvider's schema only has simple names.
2218fn strip_column_qualifiers(expr: Expr) -> Result<Expr> {
2219    expr.transform(|e| {
2220        if let Expr::Column(col) = &e
2221            && col.relation.is_some()
2222        {
2223            // Strip the qualifier
2224            return Ok(Transformed::yes(Expr::Column(Column::new_unqualified(
2225                col.name.clone(),
2226            ))));
2227        }
2228        Ok(Transformed::no(e))
2229    })
2230    .map(|t| t.data)
2231}
2232
2233/// Extract column assignments from an UPDATE input plan.
2234/// For UPDATE statements, the SQL planner encodes assignments as a projection
2235/// over the source table. This function extracts column name and expression pairs
2236/// from the projection. Column qualifiers are stripped from the expressions.
2237///
2238fn extract_update_assignments(input: &Arc<LogicalPlan>) -> Result<Vec<(String, Expr)>> {
2239    // The UPDATE input plan structure is:
2240    // Projection(updated columns as expressions with aliases)
2241    //   Filter(optional WHERE clause)
2242    //     TableScan
2243    //
2244    // Each projected expression has an alias matching the column name
2245    let mut assignments = Vec::new();
2246
2247    // Find the top-level projection
2248    if let LogicalPlan::Projection(projection) = input.as_ref() {
2249        for expr in &projection.expr {
2250            if let Expr::Alias(alias) = expr {
2251                // The alias name is the column name being updated
2252                // The inner expression is the new value
2253                let column_name = alias.name.clone();
2254                // Only include if it's not just a column reference to itself
2255                // (those are columns that aren't being updated)
2256                if !is_identity_assignment(&alias.expr, &column_name) {
2257                    // Strip qualifiers from the assignment expression
2258                    let stripped_expr = strip_column_qualifiers((*alias.expr).clone())?;
2259                    assignments.push((column_name, stripped_expr));
2260                }
2261            }
2262        }
2263    } else {
2264        // Try to find projection deeper in the plan
2265        input.apply(|node| {
2266            if let LogicalPlan::Projection(projection) = node {
2267                for expr in &projection.expr {
2268                    if let Expr::Alias(alias) = expr {
2269                        let column_name = alias.name.clone();
2270                        if !is_identity_assignment(&alias.expr, &column_name) {
2271                            let stripped_expr =
2272                                strip_column_qualifiers((*alias.expr).clone())?;
2273                            assignments.push((column_name, stripped_expr));
2274                        }
2275                    }
2276                }
2277                return Ok(TreeNodeRecursion::Stop);
2278            }
2279            Ok(TreeNodeRecursion::Continue)
2280        })?;
2281    }
2282
2283    Ok(assignments)
2284}
2285
2286/// Check if an assignment is an identity assignment (column = column)
2287/// These are columns that are not being modified in the UPDATE
2288fn is_identity_assignment(expr: &Expr, column_name: &str) -> bool {
2289    match expr {
2290        Expr::Column(col) => col.name == column_name,
2291        _ => false,
2292    }
2293}
2294
2295/// Check if window bounds are valid after schema information is available, and
2296/// window_frame bounds are casted to the corresponding column type.
2297/// queries like:
2298/// OVER (ORDER BY a RANGES BETWEEN 3 PRECEDING AND 5 PRECEDING)
2299/// OVER (ORDER BY a RANGES BETWEEN INTERVAL '3 DAY' PRECEDING AND '5 DAY' PRECEDING)  are rejected
2300pub fn is_window_frame_bound_valid(window_frame: &WindowFrame) -> bool {
2301    match (&window_frame.start_bound, &window_frame.end_bound) {
2302        (WindowFrameBound::Following(_), WindowFrameBound::Preceding(_))
2303        | (WindowFrameBound::Following(_), WindowFrameBound::CurrentRow)
2304        | (WindowFrameBound::CurrentRow, WindowFrameBound::Preceding(_)) => false,
2305        (WindowFrameBound::Preceding(lhs), WindowFrameBound::Preceding(rhs)) => {
2306            !rhs.is_null() && (lhs.is_null() || (lhs >= rhs))
2307        }
2308        (WindowFrameBound::Following(lhs), WindowFrameBound::Following(rhs)) => {
2309            !lhs.is_null() && (rhs.is_null() || (lhs <= rhs))
2310        }
2311        _ => true,
2312    }
2313}
2314
2315/// Create a window expression with a name from a logical expression
2316pub fn create_window_expr_with_name(
2317    e: &Expr,
2318    name: impl Into<String>,
2319    logical_schema: &DFSchema,
2320    execution_props: &ExecutionProps,
2321) -> Result<Arc<dyn WindowExpr>> {
2322    let name = name.into();
2323    let physical_schema = Arc::clone(logical_schema.inner());
2324    match e {
2325        Expr::WindowFunction(window_fun) => {
2326            let WindowFunction {
2327                fun,
2328                params:
2329                    WindowFunctionParams {
2330                        args,
2331                        partition_by,
2332                        order_by,
2333                        window_frame,
2334                        null_treatment,
2335                        distinct,
2336                        filter,
2337                    },
2338            } = window_fun.as_ref();
2339            let physical_args =
2340                create_physical_exprs(args, logical_schema, execution_props)?;
2341            let partition_by =
2342                create_physical_exprs(partition_by, logical_schema, execution_props)?;
2343            let order_by =
2344                create_physical_sort_exprs(order_by, logical_schema, execution_props)?;
2345
2346            if !is_window_frame_bound_valid(window_frame) {
2347                return plan_err!(
2348                    "Invalid window frame: start bound ({}) cannot be larger than end bound ({})",
2349                    window_frame.start_bound,
2350                    window_frame.end_bound
2351                );
2352            }
2353
2354            let window_frame = Arc::new(window_frame.clone());
2355            let ignore_nulls = null_treatment.unwrap_or(NullTreatment::RespectNulls)
2356                == NullTreatment::IgnoreNulls;
2357            let physical_filter = filter
2358                .as_ref()
2359                .map(|f| create_physical_expr(f, logical_schema, execution_props))
2360                .transpose()?;
2361
2362            windows::create_window_expr(
2363                fun,
2364                name,
2365                &physical_args,
2366                &partition_by,
2367                &order_by,
2368                window_frame,
2369                physical_schema,
2370                ignore_nulls,
2371                *distinct,
2372                physical_filter,
2373            )
2374        }
2375        other => plan_err!("Invalid window expression '{other:?}'"),
2376    }
2377}
2378
2379/// Create a window expression from a logical expression or an alias
2380pub fn create_window_expr(
2381    e: &Expr,
2382    logical_schema: &DFSchema,
2383    execution_props: &ExecutionProps,
2384) -> Result<Arc<dyn WindowExpr>> {
2385    // unpack aliased logical expressions, e.g. "sum(col) over () as total"
2386    let (name, e) = match e {
2387        Expr::Alias(Alias { expr, name, .. }) => (name.clone(), expr.as_ref()),
2388        _ => (e.schema_name().to_string(), e),
2389    };
2390    create_window_expr_with_name(e, name, logical_schema, execution_props)
2391}
2392
2393type AggregateExprWithOptionalArgs = (
2394    Arc<AggregateFunctionExpr>,
2395    // The filter clause, if any
2396    Option<Arc<dyn PhysicalExpr>>,
2397    // Expressions in the ORDER BY clause
2398    Vec<PhysicalSortExpr>,
2399);
2400
2401/// Create an aggregate expression with a name from a logical expression
2402pub fn create_aggregate_expr_with_name_and_maybe_filter(
2403    e: &Expr,
2404    name: Option<String>,
2405    human_displan: String,
2406    logical_input_schema: &DFSchema,
2407    physical_input_schema: &Schema,
2408    execution_props: &ExecutionProps,
2409) -> Result<AggregateExprWithOptionalArgs> {
2410    match e {
2411        Expr::AggregateFunction(AggregateFunction {
2412            func,
2413            params:
2414                AggregateFunctionParams {
2415                    args,
2416                    distinct,
2417                    filter,
2418                    order_by,
2419                    null_treatment,
2420                },
2421        }) => {
2422            let name = if let Some(name) = name {
2423                name
2424            } else {
2425                physical_name(e)?
2426            };
2427
2428            let physical_args =
2429                create_physical_exprs(args, logical_input_schema, execution_props)?;
2430            let filter = match filter {
2431                Some(e) => Some(create_physical_expr(
2432                    e,
2433                    logical_input_schema,
2434                    execution_props,
2435                )?),
2436                None => None,
2437            };
2438
2439            let ignore_nulls = null_treatment.unwrap_or(NullTreatment::RespectNulls)
2440                == NullTreatment::IgnoreNulls;
2441
2442            let (agg_expr, filter, order_bys) = {
2443                let order_bys = create_physical_sort_exprs(
2444                    order_by,
2445                    logical_input_schema,
2446                    execution_props,
2447                )?;
2448
2449                let agg_expr =
2450                    AggregateExprBuilder::new(func.to_owned(), physical_args.to_vec())
2451                        .order_by(order_bys.clone())
2452                        .schema(Arc::new(physical_input_schema.to_owned()))
2453                        .alias(name)
2454                        .human_display(human_displan)
2455                        .with_ignore_nulls(ignore_nulls)
2456                        .with_distinct(*distinct)
2457                        .build()
2458                        .map(Arc::new)?;
2459
2460                (agg_expr, filter, order_bys)
2461            };
2462
2463            Ok((agg_expr, filter, order_bys))
2464        }
2465        other => internal_err!("Invalid aggregate expression '{other:?}'"),
2466    }
2467}
2468
2469/// Create an aggregate expression from a logical expression or an alias
2470pub fn create_aggregate_expr_and_maybe_filter(
2471    e: &Expr,
2472    logical_input_schema: &DFSchema,
2473    physical_input_schema: &Schema,
2474    execution_props: &ExecutionProps,
2475) -> Result<AggregateExprWithOptionalArgs> {
2476    // Unpack (potentially nested) aliased logical expressions, e.g. "sum(col) as total"
2477    // Some functions like `count_all()` create internal aliases,
2478    // Unwrap all alias layers to get to the underlying aggregate function
2479    let (name, human_display, e) = match e {
2480        Expr::Alias(Alias { name, .. }) => {
2481            let unaliased = e.clone().unalias_nested().data;
2482            (Some(name.clone()), e.human_display().to_string(), unaliased)
2483        }
2484        Expr::AggregateFunction(_) => (
2485            Some(e.schema_name().to_string()),
2486            e.human_display().to_string(),
2487            e.clone(),
2488        ),
2489        _ => (None, String::default(), e.clone()),
2490    };
2491
2492    create_aggregate_expr_with_name_and_maybe_filter(
2493        &e,
2494        name,
2495        human_display,
2496        logical_input_schema,
2497        physical_input_schema,
2498        execution_props,
2499    )
2500}
2501
2502impl DefaultPhysicalPlanner {
2503    /// Handles capturing the various plans for EXPLAIN queries
2504    ///
2505    /// Returns
2506    /// Some(plan) if optimized, and None if logical_plan was not an
2507    /// explain (and thus needs to be optimized as normal)
2508    async fn handle_explain_or_analyze(
2509        &self,
2510        logical_plan: &LogicalPlan,
2511        session_state: &SessionState,
2512    ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
2513        let execution_plan = match logical_plan {
2514            LogicalPlan::Explain(e) => self.handle_explain(e, session_state).await?,
2515            LogicalPlan::Analyze(a) => self.handle_analyze(a, session_state).await?,
2516            _ => return Ok(None),
2517        };
2518        Ok(Some(execution_plan))
2519    }
2520
2521    /// Planner for `LogicalPlan::Explain`
2522    async fn handle_explain(
2523        &self,
2524        e: &Explain,
2525        session_state: &SessionState,
2526    ) -> Result<Arc<dyn ExecutionPlan>> {
2527        use PlanType::*;
2528        let mut stringified_plans = vec![];
2529
2530        let config = &session_state.config_options().explain;
2531        let explain_format = &e.explain_format;
2532
2533        if !e.logical_optimization_succeeded {
2534            return Ok(Arc::new(ExplainExec::new(
2535                Arc::clone(e.schema.inner()),
2536                e.stringified_plans.clone(),
2537                true,
2538            )));
2539        }
2540
2541        match explain_format {
2542            ExplainFormat::Indent => { /* fall through */ }
2543            ExplainFormat::Tree => {
2544                // Tree render does not try to explain errors,
2545                let physical_plan = self
2546                    .create_initial_plan(e.plan.as_ref(), session_state)
2547                    .await?;
2548
2549                let optimized_plan = self.optimize_physical_plan(
2550                    physical_plan,
2551                    session_state,
2552                    |_plan, _optimizer| {},
2553                )?;
2554
2555                stringified_plans.push(StringifiedPlan::new(
2556                    FinalPhysicalPlan,
2557                    displayable(optimized_plan.as_ref())
2558                        .set_tree_maximum_render_width(config.tree_maximum_render_width)
2559                        .tree_render()
2560                        .to_string(),
2561                ));
2562            }
2563            ExplainFormat::PostgresJSON => {
2564                stringified_plans.push(StringifiedPlan::new(
2565                    FinalLogicalPlan,
2566                    e.plan.display_pg_json().to_string(),
2567                ));
2568            }
2569            ExplainFormat::Graphviz => {
2570                stringified_plans.push(StringifiedPlan::new(
2571                    FinalLogicalPlan,
2572                    e.plan.display_graphviz().to_string(),
2573                ));
2574            }
2575        };
2576
2577        if !stringified_plans.is_empty() {
2578            return Ok(Arc::new(ExplainExec::new(
2579                Arc::clone(e.schema.inner()),
2580                stringified_plans,
2581                e.verbose,
2582            )));
2583        }
2584
2585        // The indent mode is quite sophisticated, and handles quite a few
2586        // different cases / options for displaying the plan.
2587        if !config.physical_plan_only {
2588            stringified_plans.clone_from(&e.stringified_plans);
2589            if e.logical_optimization_succeeded {
2590                stringified_plans.push(e.plan.to_stringified(FinalLogicalPlan));
2591            }
2592        }
2593
2594        if !config.logical_plan_only && e.logical_optimization_succeeded {
2595            match self
2596                .create_initial_plan(e.plan.as_ref(), session_state)
2597                .await
2598            {
2599                Ok(input) => {
2600                    // Include statistics / schema if enabled
2601                    stringified_plans.push(StringifiedPlan::new(
2602                        InitialPhysicalPlan,
2603                        displayable(input.as_ref())
2604                            .set_show_statistics(config.show_statistics)
2605                            .set_show_schema(config.show_schema)
2606                            .indent(e.verbose)
2607                            .to_string(),
2608                    ));
2609
2610                    // Show statistics + schema in verbose output even if not
2611                    // explicitly requested
2612                    if e.verbose {
2613                        if !config.show_statistics {
2614                            stringified_plans.push(StringifiedPlan::new(
2615                                InitialPhysicalPlanWithStats,
2616                                displayable(input.as_ref())
2617                                    .set_show_statistics(true)
2618                                    .indent(e.verbose)
2619                                    .to_string(),
2620                            ));
2621                        }
2622                        if !config.show_schema {
2623                            stringified_plans.push(StringifiedPlan::new(
2624                                InitialPhysicalPlanWithSchema,
2625                                displayable(input.as_ref())
2626                                    .set_show_schema(true)
2627                                    .indent(e.verbose)
2628                                    .to_string(),
2629                            ));
2630                        }
2631                    }
2632
2633                    let optimized_plan = self.optimize_physical_plan(
2634                        input,
2635                        session_state,
2636                        |plan, optimizer| {
2637                            let optimizer_name = optimizer.name().to_string();
2638                            let plan_type = OptimizedPhysicalPlan { optimizer_name };
2639                            stringified_plans.push(StringifiedPlan::new(
2640                                plan_type,
2641                                displayable(plan)
2642                                    .set_show_statistics(config.show_statistics)
2643                                    .set_show_schema(config.show_schema)
2644                                    .indent(e.verbose)
2645                                    .to_string(),
2646                            ));
2647                        },
2648                    );
2649                    match optimized_plan {
2650                        Ok(input) => {
2651                            // This plan will includes statistics if show_statistics is on
2652                            stringified_plans.push(StringifiedPlan::new(
2653                                FinalPhysicalPlan,
2654                                displayable(input.as_ref())
2655                                    .set_show_statistics(config.show_statistics)
2656                                    .set_show_schema(config.show_schema)
2657                                    .indent(e.verbose)
2658                                    .to_string(),
2659                            ));
2660
2661                            // Show statistics + schema in verbose output even if not
2662                            // explicitly requested
2663                            if e.verbose {
2664                                if !config.show_statistics {
2665                                    stringified_plans.push(StringifiedPlan::new(
2666                                        FinalPhysicalPlanWithStats,
2667                                        displayable(input.as_ref())
2668                                            .set_show_statistics(true)
2669                                            .indent(e.verbose)
2670                                            .to_string(),
2671                                    ));
2672                                }
2673                                if !config.show_schema {
2674                                    stringified_plans.push(StringifiedPlan::new(
2675                                        FinalPhysicalPlanWithSchema,
2676                                        // This will include schema if show_schema is on
2677                                        // and will be set to true if verbose is on
2678                                        displayable(input.as_ref())
2679                                            .set_show_schema(true)
2680                                            .indent(e.verbose)
2681                                            .to_string(),
2682                                    ));
2683                                }
2684                            }
2685                        }
2686                        Err(DataFusionError::Context(optimizer_name, e)) => {
2687                            let plan_type = OptimizedPhysicalPlan { optimizer_name };
2688                            stringified_plans
2689                                .push(StringifiedPlan::new(plan_type, e.to_string()))
2690                        }
2691                        Err(e) => return Err(e),
2692                    }
2693                }
2694                Err(err) => {
2695                    stringified_plans.push(StringifiedPlan::new(
2696                        PhysicalPlanError,
2697                        err.strip_backtrace(),
2698                    ));
2699                }
2700            }
2701        }
2702
2703        Ok(Arc::new(ExplainExec::new(
2704            Arc::clone(e.schema.inner()),
2705            stringified_plans,
2706            e.verbose,
2707        )))
2708    }
2709
2710    async fn handle_analyze(
2711        &self,
2712        a: &Analyze,
2713        session_state: &SessionState,
2714    ) -> Result<Arc<dyn ExecutionPlan>> {
2715        let input = self.create_physical_plan(&a.input, session_state).await?;
2716        let schema = Arc::clone(a.schema.inner());
2717        let show_statistics = session_state.config_options().explain.show_statistics;
2718        let analyze_level = session_state.config_options().explain.analyze_level;
2719        let metric_types = match analyze_level {
2720            ExplainAnalyzeLevel::Summary => vec![MetricType::SUMMARY],
2721            ExplainAnalyzeLevel::Dev => vec![MetricType::SUMMARY, MetricType::DEV],
2722        };
2723        Ok(Arc::new(AnalyzeExec::new(
2724            a.verbose,
2725            show_statistics,
2726            metric_types,
2727            input,
2728            schema,
2729        )))
2730    }
2731
2732    /// Optimize a physical plan by applying each physical optimizer,
2733    /// calling observer(plan, optimizer after each one)
2734    #[expect(clippy::needless_pass_by_value)]
2735    pub fn optimize_physical_plan<F>(
2736        &self,
2737        plan: Arc<dyn ExecutionPlan>,
2738        session_state: &SessionState,
2739        mut observer: F,
2740    ) -> Result<Arc<dyn ExecutionPlan>>
2741    where
2742        F: FnMut(&dyn ExecutionPlan, &dyn PhysicalOptimizerRule),
2743    {
2744        let optimizers = session_state.physical_optimizers();
2745        debug!(
2746            "Input physical plan:\n{}\n",
2747            displayable(plan.as_ref()).indent(false)
2748        );
2749        debug!(
2750            "Detailed input physical plan:\n{}",
2751            displayable(plan.as_ref()).indent(true)
2752        );
2753
2754        // This runs once before any optimization,
2755        // to verify that the plan fulfills the base requirements.
2756        InvariantChecker(InvariantLevel::Always).check(&plan)?;
2757
2758        let mut new_plan = Arc::clone(&plan);
2759        for optimizer in optimizers {
2760            let before_schema = new_plan.schema();
2761            new_plan = optimizer
2762                .optimize(new_plan, session_state.config_options())
2763                .map_err(|e| {
2764                    DataFusionError::Context(optimizer.name().to_string(), Box::new(e))
2765                })?;
2766
2767            // This only checks the schema in release build, and performs additional checks in debug mode.
2768            OptimizationInvariantChecker::new(optimizer)
2769                .check(&new_plan, &before_schema)?;
2770
2771            debug!(
2772                "Optimized physical plan by {}:\n{}\n",
2773                optimizer.name(),
2774                displayable(new_plan.as_ref()).indent(false)
2775            );
2776            observer(new_plan.as_ref(), optimizer.as_ref())
2777        }
2778
2779        // This runs once after all optimizer runs are complete,
2780        // to verify that the plan is executable.
2781        InvariantChecker(InvariantLevel::Executable).check(&new_plan)?;
2782
2783        debug!(
2784            "Optimized physical plan:\n{}\n",
2785            displayable(new_plan.as_ref()).indent(false)
2786        );
2787
2788        // Don't print new_plan directly, as that may overflow the stack.
2789        // For example:
2790        // thread 'tokio-runtime-worker' has overflowed its stack
2791        // fatal runtime error: stack overflow, aborting
2792        debug!(
2793            "Detailed optimized physical plan:\n{}\n",
2794            displayable(new_plan.as_ref()).indent(true)
2795        );
2796        Ok(new_plan)
2797    }
2798
2799    // return an record_batch which describes a table's schema.
2800    fn plan_describe(
2801        &self,
2802        table_schema: &Arc<Schema>,
2803        output_schema: Arc<Schema>,
2804    ) -> Result<Arc<dyn ExecutionPlan>> {
2805        let mut column_names = StringBuilder::new();
2806        let mut data_types = StringBuilder::new();
2807        let mut is_nullables = StringBuilder::new();
2808        for field in table_schema.fields() {
2809            column_names.append_value(field.name());
2810
2811            // "System supplied type" --> Use debug format of the datatype
2812            let data_type = field.data_type();
2813            data_types.append_value(format!("{data_type}"));
2814
2815            // "YES if the column is possibly nullable, NO if it is known not nullable. "
2816            let nullable_str = if field.is_nullable() { "YES" } else { "NO" };
2817            is_nullables.append_value(nullable_str);
2818        }
2819
2820        let record_batch = RecordBatch::try_new(
2821            output_schema,
2822            vec![
2823                Arc::new(column_names.finish()),
2824                Arc::new(data_types.finish()),
2825                Arc::new(is_nullables.finish()),
2826            ],
2827        )?;
2828
2829        let schema = record_batch.schema();
2830        let partitions = vec![vec![record_batch]];
2831        let projection = None;
2832        let mem_exec = MemorySourceConfig::try_new_exec(&partitions, schema, projection)?;
2833        Ok(mem_exec)
2834    }
2835
2836    fn create_project_physical_exec(
2837        &self,
2838        session_state: &SessionState,
2839        input_exec: Arc<dyn ExecutionPlan>,
2840        input: &Arc<LogicalPlan>,
2841        expr: &[Expr],
2842    ) -> Result<Arc<dyn ExecutionPlan>> {
2843        let input_logical_schema = input.as_ref().schema();
2844        let input_physical_schema = input_exec.schema();
2845        let physical_exprs = expr
2846            .iter()
2847            .map(|e| {
2848                // For projections, SQL planner and logical plan builder may convert user
2849                // provided expressions into logical Column expressions if their results
2850                // are already provided from the input plans. Because we work with
2851                // qualified columns in logical plane, derived columns involve operators or
2852                // functions will contain qualifiers as well. This will result in logical
2853                // columns with names like `SUM(t1.c1)`, `t1.c1 + t1.c2`, etc.
2854                //
2855                // If we run these logical columns through physical_name function, we will
2856                // get physical names with column qualifiers, which violates DataFusion's
2857                // field name semantics. To account for this, we need to derive the
2858                // physical name from physical input instead.
2859                //
2860                // This depends on the invariant that logical schema field index MUST match
2861                // with physical schema field index.
2862                let physical_name = if let Expr::Column(col) = e {
2863                    match input_logical_schema.index_of_column(col) {
2864                        Ok(idx) => {
2865                            // index physical field using logical field index
2866                            Ok(input_exec.schema().field(idx).name().to_string())
2867                        }
2868                        // logical column is not a derived column, safe to pass along to
2869                        // physical_name
2870                        Err(_) => physical_name(e),
2871                    }
2872                } else {
2873                    physical_name(e)
2874                };
2875
2876                let physical_expr =
2877                    self.create_physical_expr(e, input_logical_schema, session_state);
2878
2879                tuple_err((physical_expr, physical_name))
2880            })
2881            .collect::<Result<Vec<_>>>()?;
2882
2883        let num_input_columns = input_exec.schema().fields().len();
2884
2885        match self.try_plan_async_exprs(
2886            num_input_columns,
2887            PlannedExprResult::ExprWithName(physical_exprs),
2888            input_physical_schema.as_ref(),
2889        )? {
2890            PlanAsyncExpr::Sync(PlannedExprResult::ExprWithName(physical_exprs)) => {
2891                let proj_exprs: Vec<ProjectionExpr> = physical_exprs
2892                    .into_iter()
2893                    .map(|(expr, alias)| ProjectionExpr { expr, alias })
2894                    .collect();
2895                Ok(Arc::new(ProjectionExec::try_new(proj_exprs, input_exec)?))
2896            }
2897            PlanAsyncExpr::Async(
2898                async_map,
2899                PlannedExprResult::ExprWithName(physical_exprs),
2900            ) => {
2901                let async_exec =
2902                    AsyncFuncExec::try_new(async_map.async_exprs, input_exec)?;
2903                let proj_exprs: Vec<ProjectionExpr> = physical_exprs
2904                    .into_iter()
2905                    .map(|(expr, alias)| ProjectionExpr { expr, alias })
2906                    .collect();
2907                let new_proj_exec =
2908                    ProjectionExec::try_new(proj_exprs, Arc::new(async_exec))?;
2909                Ok(Arc::new(new_proj_exec))
2910            }
2911            _ => internal_err!("Unexpected PlanAsyncExpressions variant"),
2912        }
2913    }
2914
2915    fn try_plan_async_exprs(
2916        &self,
2917        num_input_columns: usize,
2918        physical_expr: PlannedExprResult,
2919        schema: &Schema,
2920    ) -> Result<PlanAsyncExpr> {
2921        let mut async_map = AsyncMapper::new(num_input_columns);
2922        match &physical_expr {
2923            PlannedExprResult::ExprWithName(exprs) => {
2924                exprs
2925                    .iter()
2926                    .try_for_each(|(expr, _)| async_map.find_references(expr, schema))?;
2927            }
2928            PlannedExprResult::Expr(exprs) => {
2929                exprs
2930                    .iter()
2931                    .try_for_each(|expr| async_map.find_references(expr, schema))?;
2932            }
2933        }
2934
2935        if async_map.is_empty() {
2936            return Ok(PlanAsyncExpr::Sync(physical_expr));
2937        }
2938
2939        let new_exprs = match physical_expr {
2940            PlannedExprResult::ExprWithName(exprs) => PlannedExprResult::ExprWithName(
2941                exprs
2942                    .iter()
2943                    .map(|(expr, column_name)| {
2944                        let new_expr = Arc::clone(expr)
2945                            .transform_up(|e| Ok(async_map.map_expr(e)))?;
2946                        Ok((new_expr.data, column_name.to_string()))
2947                    })
2948                    .collect::<Result<_>>()?,
2949            ),
2950            PlannedExprResult::Expr(exprs) => PlannedExprResult::Expr(
2951                exprs
2952                    .iter()
2953                    .map(|expr| {
2954                        let new_expr = Arc::clone(expr)
2955                            .transform_up(|e| Ok(async_map.map_expr(e)))?;
2956                        Ok(new_expr.data)
2957                    })
2958                    .collect::<Result<_>>()?,
2959            ),
2960        };
2961        // rewrite the projection's expressions in terms of the columns with the result of async evaluation
2962        Ok(PlanAsyncExpr::Async(async_map, new_exprs))
2963    }
2964}
2965
2966#[derive(Debug)]
2967enum PlannedExprResult {
2968    ExprWithName(Vec<(Arc<dyn PhysicalExpr>, String)>),
2969    Expr(Vec<Arc<dyn PhysicalExpr>>),
2970}
2971
2972#[derive(Debug)]
2973enum PlanAsyncExpr {
2974    Sync(PlannedExprResult),
2975    Async(AsyncMapper, PlannedExprResult),
2976}
2977
2978fn tuple_err<T, R>(value: (Result<T>, Result<R>)) -> Result<(T, R)> {
2979    match value {
2980        (Ok(e), Ok(e1)) => Ok((e, e1)),
2981        (Err(e), Ok(_)) => Err(e),
2982        (Ok(_), Err(e1)) => Err(e1),
2983        (Err(e), Err(_)) => Err(e),
2984    }
2985}
2986
2987struct OptimizationInvariantChecker<'a> {
2988    rule: &'a Arc<dyn PhysicalOptimizerRule + Send + Sync>,
2989}
2990
2991impl<'a> OptimizationInvariantChecker<'a> {
2992    /// Create an [`OptimizationInvariantChecker`] that performs checking per tule.
2993    pub fn new(rule: &'a Arc<dyn PhysicalOptimizerRule + Send + Sync>) -> Self {
2994        Self { rule }
2995    }
2996
2997    /// Checks that the plan change is permitted, returning an Error if not.
2998    ///
2999    /// Conditionally performs schema checks per [PhysicalOptimizerRule::schema_check].
3000    /// In debug mode, this recursively walks the entire physical plan
3001    /// and performs [`ExecutionPlan::check_invariants`].
3002    pub fn check(
3003        &mut self,
3004        plan: &Arc<dyn ExecutionPlan>,
3005        previous_schema: &Arc<Schema>,
3006    ) -> Result<()> {
3007        // if the rule is not permitted to change the schema, confirm that it did not change.
3008        if self.rule.schema_check()
3009            && !is_allowed_schema_change(previous_schema.as_ref(), plan.schema().as_ref())
3010        {
3011            internal_err!(
3012                "PhysicalOptimizer rule '{}' failed. Schema mismatch. Expected original schema: {}, got new schema: {}",
3013                self.rule.name(),
3014                previous_schema,
3015                plan.schema()
3016            )?
3017        }
3018
3019        // check invariants per each ExecutionPlan node
3020        #[cfg(debug_assertions)]
3021        plan.visit(self)?;
3022
3023        Ok(())
3024    }
3025}
3026
3027/// Checks if the change from `old` schema to `new` is allowed or not.
3028///
3029/// The current implementation only allows nullability of individual fields to change
3030/// from 'nullable' to 'not nullable'. This can happen due to physical expressions knowing
3031/// more about their null-ness than their logical counterparts.
3032/// This change is allowed because for any field the non-nullable domain `F` is a strict subset
3033/// of the nullable domain `F ∪ { NULL }`. A physical schema that guarantees a stricter subset
3034/// of values will not violate any assumptions made based on the less strict schema.
3035fn is_allowed_schema_change(old: &Schema, new: &Schema) -> bool {
3036    if new.metadata != old.metadata {
3037        return false;
3038    }
3039
3040    if new.fields.len() != old.fields.len() {
3041        return false;
3042    }
3043
3044    let new_fields = new.fields.iter().map(|f| f.as_ref());
3045    let old_fields = old.fields.iter().map(|f| f.as_ref());
3046    old_fields
3047        .zip(new_fields)
3048        .all(|(old, new)| is_allowed_field_change(old, new))
3049}
3050
3051fn is_allowed_field_change(old_field: &Field, new_field: &Field) -> bool {
3052    new_field.name() == old_field.name()
3053        && new_field.data_type() == old_field.data_type()
3054        && new_field.metadata() == old_field.metadata()
3055        && (new_field.is_nullable() == old_field.is_nullable()
3056            || !new_field.is_nullable())
3057}
3058
3059impl<'n> TreeNodeVisitor<'n> for OptimizationInvariantChecker<'_> {
3060    type Node = Arc<dyn ExecutionPlan>;
3061
3062    fn f_down(&mut self, node: &'n Self::Node) -> Result<TreeNodeRecursion> {
3063        // Checks for the more permissive `InvariantLevel::Always`.
3064        // Plans are not guaranteed to be executable after each physical optimizer run.
3065        node.check_invariants(InvariantLevel::Always).map_err(|e|
3066            e.context(format!("Invariant for ExecutionPlan node '{}' failed for PhysicalOptimizer rule '{}'", node.name(), self.rule.name()))
3067        )?;
3068        Ok(TreeNodeRecursion::Continue)
3069    }
3070}
3071
3072/// Check [`ExecutionPlan`] invariants per [`InvariantLevel`].
3073struct InvariantChecker(InvariantLevel);
3074
3075impl InvariantChecker {
3076    /// Checks that the plan is executable, returning an Error if not.
3077    pub fn check(&mut self, plan: &Arc<dyn ExecutionPlan>) -> Result<()> {
3078        // check invariants per each ExecutionPlan node
3079        plan.visit(self)?;
3080
3081        Ok(())
3082    }
3083}
3084
3085impl<'n> TreeNodeVisitor<'n> for InvariantChecker {
3086    type Node = Arc<dyn ExecutionPlan>;
3087
3088    fn f_down(&mut self, node: &'n Self::Node) -> Result<TreeNodeRecursion> {
3089        node.check_invariants(self.0).map_err(|e| {
3090            e.context(format!(
3091                "Invariant for ExecutionPlan node '{}' failed",
3092                node.name()
3093            ))
3094        })?;
3095        Ok(TreeNodeRecursion::Continue)
3096    }
3097}
3098
3099#[cfg(test)]
3100mod tests {
3101    use std::any::Any;
3102    use std::cmp::Ordering;
3103    use std::fmt::{self, Debug};
3104    use std::ops::{BitAnd, Not};
3105
3106    use super::*;
3107    use crate::datasource::MemTable;
3108    use crate::datasource::file_format::options::CsvReadOptions;
3109    use crate::physical_plan::{
3110        DisplayAs, DisplayFormatType, PlanProperties, SendableRecordBatchStream,
3111        expressions,
3112    };
3113    use crate::prelude::{SessionConfig, SessionContext};
3114    use crate::test_util::{scan_empty, scan_empty_with_partitions};
3115
3116    use crate::execution::session_state::SessionStateBuilder;
3117    use arrow::array::{ArrayRef, DictionaryArray, Int32Array};
3118    use arrow::datatypes::{DataType, Field, Int32Type};
3119    use arrow_schema::SchemaRef;
3120    use datafusion_common::config::ConfigOptions;
3121    use datafusion_common::{
3122        DFSchemaRef, TableReference, ToDFSchema as _, assert_contains,
3123    };
3124    use datafusion_execution::TaskContext;
3125    use datafusion_execution::runtime_env::RuntimeEnv;
3126    use datafusion_expr::builder::subquery_alias;
3127    use datafusion_expr::{
3128        LogicalPlanBuilder, TableSource, UserDefinedLogicalNodeCore, col, lit,
3129    };
3130    use datafusion_functions_aggregate::count::count_all;
3131    use datafusion_functions_aggregate::expr_fn::sum;
3132    use datafusion_physical_expr::EquivalenceProperties;
3133    use datafusion_physical_plan::execution_plan::{Boundedness, EmissionType};
3134
3135    fn make_session_state() -> SessionState {
3136        let runtime = Arc::new(RuntimeEnv::default());
3137        let config = SessionConfig::new().with_target_partitions(4);
3138        let config = config.set_bool("datafusion.optimizer.skip_failed_rules", false);
3139        SessionStateBuilder::new()
3140            .with_config(config)
3141            .with_runtime_env(runtime)
3142            .with_default_features()
3143            .build()
3144    }
3145
3146    async fn plan(logical_plan: &LogicalPlan) -> Result<Arc<dyn ExecutionPlan>> {
3147        let session_state = make_session_state();
3148        // optimize the logical plan
3149        let logical_plan = session_state.optimize(logical_plan)?;
3150        let planner = DefaultPhysicalPlanner::default();
3151        planner
3152            .create_physical_plan(&logical_plan, &session_state)
3153            .await
3154    }
3155
3156    #[tokio::test]
3157    async fn test_all_operators() -> Result<()> {
3158        let logical_plan = test_csv_scan()
3159            .await?
3160            // filter clause needs the type coercion rule applied
3161            .filter(col("c7").lt(lit(5_u8)))?
3162            .project(vec![col("c1"), col("c2")])?
3163            .aggregate(vec![col("c1")], vec![sum(col("c2"))])?
3164            .sort(vec![col("c1").sort(true, true)])?
3165            .limit(3, Some(10))?
3166            .build()?;
3167
3168        let exec_plan = plan(&logical_plan).await?;
3169
3170        // verify that the plan correctly casts u8 to i64
3171        // the cast from u8 to i64 for literal will be simplified, and get lit(int64(5))
3172        // the cast here is implicit so has CastOptions with safe=true
3173        let expected = r#"BinaryExpr { left: Column { name: "c7", index: 2 }, op: Lt, right: Literal { value: Int64(5), field: Field { name: "lit", data_type: Int64 } }, fail_on_overflow: false"#;
3174
3175        assert_contains!(format!("{exec_plan:?}"), expected);
3176        Ok(())
3177    }
3178
3179    #[tokio::test]
3180    async fn test_create_cube_expr() -> Result<()> {
3181        let logical_plan = test_csv_scan().await?.build()?;
3182
3183        let plan = plan(&logical_plan).await?;
3184
3185        let exprs = vec![col("c1"), col("c2"), col("c3")];
3186
3187        let physical_input_schema = plan.schema();
3188        let physical_input_schema = physical_input_schema.as_ref();
3189        let logical_input_schema = logical_plan.schema();
3190        let session_state = make_session_state();
3191
3192        let cube = create_cube_physical_expr(
3193            &exprs,
3194            logical_input_schema,
3195            physical_input_schema,
3196            &session_state,
3197        );
3198
3199        insta::assert_debug_snapshot!(cube, @r#"
3200        Ok(
3201            PhysicalGroupBy {
3202                expr: [
3203                    (
3204                        Column {
3205                            name: "c1",
3206                            index: 0,
3207                        },
3208                        "c1",
3209                    ),
3210                    (
3211                        Column {
3212                            name: "c2",
3213                            index: 1,
3214                        },
3215                        "c2",
3216                    ),
3217                    (
3218                        Column {
3219                            name: "c3",
3220                            index: 2,
3221                        },
3222                        "c3",
3223                    ),
3224                ],
3225                null_expr: [
3226                    (
3227                        Literal {
3228                            value: Utf8(NULL),
3229                            field: Field {
3230                                name: "lit",
3231                                data_type: Utf8,
3232                                nullable: true,
3233                            },
3234                        },
3235                        "c1",
3236                    ),
3237                    (
3238                        Literal {
3239                            value: Int64(NULL),
3240                            field: Field {
3241                                name: "lit",
3242                                data_type: Int64,
3243                                nullable: true,
3244                            },
3245                        },
3246                        "c2",
3247                    ),
3248                    (
3249                        Literal {
3250                            value: Int64(NULL),
3251                            field: Field {
3252                                name: "lit",
3253                                data_type: Int64,
3254                                nullable: true,
3255                            },
3256                        },
3257                        "c3",
3258                    ),
3259                ],
3260                groups: [
3261                    [
3262                        false,
3263                        false,
3264                        false,
3265                    ],
3266                    [
3267                        true,
3268                        false,
3269                        false,
3270                    ],
3271                    [
3272                        false,
3273                        true,
3274                        false,
3275                    ],
3276                    [
3277                        false,
3278                        false,
3279                        true,
3280                    ],
3281                    [
3282                        true,
3283                        true,
3284                        false,
3285                    ],
3286                    [
3287                        true,
3288                        false,
3289                        true,
3290                    ],
3291                    [
3292                        false,
3293                        true,
3294                        true,
3295                    ],
3296                    [
3297                        true,
3298                        true,
3299                        true,
3300                    ],
3301                ],
3302                has_grouping_set: true,
3303            },
3304        )
3305        "#);
3306
3307        Ok(())
3308    }
3309
3310    #[tokio::test]
3311    async fn test_create_rollup_expr() -> Result<()> {
3312        let logical_plan = test_csv_scan().await?.build()?;
3313
3314        let plan = plan(&logical_plan).await?;
3315
3316        let exprs = vec![col("c1"), col("c2"), col("c3")];
3317
3318        let physical_input_schema = plan.schema();
3319        let physical_input_schema = physical_input_schema.as_ref();
3320        let logical_input_schema = logical_plan.schema();
3321        let session_state = make_session_state();
3322
3323        let rollup = create_rollup_physical_expr(
3324            &exprs,
3325            logical_input_schema,
3326            physical_input_schema,
3327            &session_state,
3328        );
3329
3330        insta::assert_debug_snapshot!(rollup, @r#"
3331        Ok(
3332            PhysicalGroupBy {
3333                expr: [
3334                    (
3335                        Column {
3336                            name: "c1",
3337                            index: 0,
3338                        },
3339                        "c1",
3340                    ),
3341                    (
3342                        Column {
3343                            name: "c2",
3344                            index: 1,
3345                        },
3346                        "c2",
3347                    ),
3348                    (
3349                        Column {
3350                            name: "c3",
3351                            index: 2,
3352                        },
3353                        "c3",
3354                    ),
3355                ],
3356                null_expr: [
3357                    (
3358                        Literal {
3359                            value: Utf8(NULL),
3360                            field: Field {
3361                                name: "lit",
3362                                data_type: Utf8,
3363                                nullable: true,
3364                            },
3365                        },
3366                        "c1",
3367                    ),
3368                    (
3369                        Literal {
3370                            value: Int64(NULL),
3371                            field: Field {
3372                                name: "lit",
3373                                data_type: Int64,
3374                                nullable: true,
3375                            },
3376                        },
3377                        "c2",
3378                    ),
3379                    (
3380                        Literal {
3381                            value: Int64(NULL),
3382                            field: Field {
3383                                name: "lit",
3384                                data_type: Int64,
3385                                nullable: true,
3386                            },
3387                        },
3388                        "c3",
3389                    ),
3390                ],
3391                groups: [
3392                    [
3393                        true,
3394                        true,
3395                        true,
3396                    ],
3397                    [
3398                        false,
3399                        true,
3400                        true,
3401                    ],
3402                    [
3403                        false,
3404                        false,
3405                        true,
3406                    ],
3407                    [
3408                        false,
3409                        false,
3410                        false,
3411                    ],
3412                ],
3413                has_grouping_set: true,
3414            },
3415        )
3416        "#);
3417
3418        Ok(())
3419    }
3420
3421    #[tokio::test]
3422    async fn test_create_not() -> Result<()> {
3423        let schema = Schema::new(vec![Field::new("a", DataType::Boolean, true)]);
3424        let dfschema = DFSchema::try_from(schema.clone())?;
3425
3426        let planner = DefaultPhysicalPlanner::default();
3427
3428        let expr = planner.create_physical_expr(
3429            &col("a").not(),
3430            &dfschema,
3431            &make_session_state(),
3432        )?;
3433        let expected = expressions::not(expressions::col("a", &schema)?)?;
3434
3435        assert_eq!(format!("{expr:?}"), format!("{expected:?}"));
3436
3437        Ok(())
3438    }
3439
3440    #[tokio::test]
3441    async fn test_with_csv_plan() -> Result<()> {
3442        let logical_plan = test_csv_scan()
3443            .await?
3444            .filter(col("c7").lt(col("c12")))?
3445            .limit(3, None)?
3446            .build()?;
3447
3448        let plan = plan(&logical_plan).await?;
3449
3450        // c12 is f64, c7 is u8 -> cast c7 to f64
3451        // the cast here is implicit so has CastOptions with safe=true
3452        let _expected = "predicate: BinaryExpr { left: TryCastExpr { expr: Column { name: \"c7\", index: 6 }, cast_type: Float64 }, op: Lt, right: Column { name: \"c12\", index: 11 } }";
3453        let plan_debug_str = format!("{plan:?}");
3454        assert!(plan_debug_str.contains("GlobalLimitExec"));
3455        assert!(plan_debug_str.contains("skip: 3"));
3456        Ok(())
3457    }
3458
3459    #[tokio::test]
3460    async fn error_during_extension_planning() {
3461        let session_state = make_session_state();
3462        let planner = DefaultPhysicalPlanner::with_extension_planners(vec![Arc::new(
3463            ErrorExtensionPlanner {},
3464        )]);
3465
3466        let logical_plan = LogicalPlan::Extension(Extension {
3467            node: Arc::new(NoOpExtensionNode::default()),
3468        });
3469        match planner
3470            .create_physical_plan(&logical_plan, &session_state)
3471            .await
3472        {
3473            Ok(_) => panic!("Expected planning failure"),
3474            Err(e) => assert!(e.to_string().contains("BOOM"),),
3475        }
3476    }
3477
3478    #[tokio::test]
3479    async fn test_with_zero_offset_plan() -> Result<()> {
3480        let logical_plan = test_csv_scan().await?.limit(0, None)?.build()?;
3481        let plan = plan(&logical_plan).await?;
3482        assert!(!format!("{plan:?}").contains("limit="));
3483        Ok(())
3484    }
3485
3486    #[tokio::test]
3487    async fn test_limit_with_partitions() -> Result<()> {
3488        let schema = Schema::new(vec![Field::new("id", DataType::Int32, false)]);
3489
3490        let logical_plan = scan_empty_with_partitions(Some("test"), &schema, None, 2)?
3491            .limit(3, Some(5))?
3492            .build()?;
3493        let plan = plan(&logical_plan).await?;
3494
3495        assert!(format!("{plan:?}").contains("GlobalLimitExec"));
3496        assert!(format!("{plan:?}").contains("skip: 3, fetch: Some(5)"));
3497
3498        Ok(())
3499    }
3500
3501    #[tokio::test]
3502    async fn errors() -> Result<()> {
3503        let bool_expr = col("c1").eq(col("c1"));
3504        let cases = vec![
3505            // utf8 = utf8
3506            col("c1").eq(col("c1")),
3507            // u8 AND u8
3508            col("c3").bitand(col("c3")),
3509            // utf8 = u8
3510            col("c1").eq(col("c3")),
3511            // bool AND bool
3512            bool_expr.clone().and(bool_expr),
3513        ];
3514        for case in cases {
3515            test_csv_scan().await?.project(vec![case.clone()]).unwrap();
3516        }
3517        Ok(())
3518    }
3519
3520    #[tokio::test]
3521    async fn default_extension_planner() {
3522        let session_state = make_session_state();
3523        let planner = DefaultPhysicalPlanner::default();
3524        let logical_plan = LogicalPlan::Extension(Extension {
3525            node: Arc::new(NoOpExtensionNode::default()),
3526        });
3527        let plan = planner
3528            .create_physical_plan(&logical_plan, &session_state)
3529            .await;
3530
3531        let expected_error = "No installed planner was able to convert the custom node to an execution plan: NoOp";
3532        match plan {
3533            Ok(_) => panic!("Expected planning failure"),
3534            Err(e) => assert!(
3535                e.to_string().contains(expected_error),
3536                "Error '{e}' did not contain expected error '{expected_error}'"
3537            ),
3538        }
3539    }
3540
3541    #[tokio::test]
3542    async fn bad_extension_planner() {
3543        // Test that creating an execution plan whose schema doesn't
3544        // match the logical plan's schema generates an error.
3545        let session_state = make_session_state();
3546        let planner = DefaultPhysicalPlanner::with_extension_planners(vec![Arc::new(
3547            BadExtensionPlanner {},
3548        )]);
3549
3550        let logical_plan = LogicalPlan::Extension(Extension {
3551            node: Arc::new(NoOpExtensionNode::default()),
3552        });
3553        let e = planner
3554            .create_physical_plan(&logical_plan, &session_state)
3555            .await
3556            .expect_err("planning error")
3557            .strip_backtrace();
3558
3559        insta::assert_snapshot!(e, @r#"Error during planning: Extension planner for NoOp created an ExecutionPlan with mismatched schema. LogicalPlan schema: DFSchema { inner: Schema { fields: [Field { name: "a", data_type: Int32 }], metadata: {} }, field_qualifiers: [None], functional_dependencies: FunctionalDependencies { deps: [] } }, ExecutionPlan schema: Schema { fields: [Field { name: "b", data_type: Int32 }], metadata: {} }"#);
3560    }
3561
3562    #[tokio::test]
3563    async fn in_list_types() -> Result<()> {
3564        // expression: "a in ('a', 1)"
3565        let list = vec![lit("a"), lit(1i64)];
3566        let logical_plan = test_csv_scan()
3567            .await?
3568            // filter clause needs the type coercion rule applied
3569            .filter(col("c12").lt(lit(0.05)))?
3570            .project(vec![col("c1").in_list(list, false)])?
3571            .build()?;
3572        let execution_plan = plan(&logical_plan).await?;
3573        // verify that the plan correctly adds cast from Int64(1) to Utf8, and the const will be evaluated.
3574
3575        let expected = r#"expr: BinaryExpr { left: BinaryExpr { left: Column { name: "c1", index: 0 }, op: Eq, right: Literal { value: Utf8("a"), field: Field { name: "lit", data_type: Utf8 } }, fail_on_overflow: false }"#;
3576
3577        assert_contains!(format!("{execution_plan:?}"), expected);
3578
3579        Ok(())
3580    }
3581
3582    #[tokio::test]
3583    async fn in_list_types_struct_literal() -> Result<()> {
3584        // expression: "a in (struct::null, 'a')"
3585        let list = vec![struct_literal(), lit("a")];
3586
3587        let logical_plan = test_csv_scan()
3588            .await?
3589            // filter clause needs the type coercion rule applied
3590            .filter(col("c12").lt(lit(0.05)))?
3591            .project(vec![col("c12").lt_eq(lit(0.025)).in_list(list, false)])?
3592            .build()?;
3593        let e = plan(&logical_plan).await.unwrap_err().to_string();
3594
3595        assert_contains!(
3596            &e,
3597            r#"Error during planning: Can not find compatible types to compare Boolean with [Struct("foo": non-null Boolean), Utf8]"#
3598        );
3599
3600        Ok(())
3601    }
3602
3603    /// Return a `null` literal representing a struct type like: `{ a: bool }`
3604    fn struct_literal() -> Expr {
3605        let struct_literal = ScalarValue::try_from(DataType::Struct(
3606            vec![Field::new("foo", DataType::Boolean, false)].into(),
3607        ))
3608        .unwrap();
3609
3610        lit(struct_literal)
3611    }
3612
3613    #[tokio::test]
3614    async fn hash_agg_input_schema() -> Result<()> {
3615        let logical_plan = test_csv_scan_with_name("aggregate_test_100")
3616            .await?
3617            .aggregate(vec![col("c1")], vec![sum(col("c2"))])?
3618            .build()?;
3619
3620        let execution_plan = plan(&logical_plan).await?;
3621        let final_hash_agg = execution_plan
3622            .as_any()
3623            .downcast_ref::<AggregateExec>()
3624            .expect("hash aggregate");
3625        assert_eq!(
3626            "sum(aggregate_test_100.c2)",
3627            final_hash_agg.schema().field(1).name()
3628        );
3629        // we need access to the input to the partial aggregate so that other projects can
3630        // implement serde
3631        assert_eq!("c2", final_hash_agg.input_schema().field(1).name());
3632
3633        Ok(())
3634    }
3635
3636    #[tokio::test]
3637    async fn hash_agg_grouping_set_input_schema() -> Result<()> {
3638        let grouping_set_expr = Expr::GroupingSet(GroupingSet::GroupingSets(vec![
3639            vec![col("c1")],
3640            vec![col("c2")],
3641            vec![col("c1"), col("c2")],
3642        ]));
3643        let logical_plan = test_csv_scan_with_name("aggregate_test_100")
3644            .await?
3645            .aggregate(vec![grouping_set_expr], vec![sum(col("c3"))])?
3646            .build()?;
3647
3648        let execution_plan = plan(&logical_plan).await?;
3649        let final_hash_agg = execution_plan
3650            .as_any()
3651            .downcast_ref::<AggregateExec>()
3652            .expect("hash aggregate");
3653        assert_eq!(
3654            "sum(aggregate_test_100.c3)",
3655            final_hash_agg.schema().field(3).name()
3656        );
3657        // we need access to the input to the partial aggregate so that other projects can
3658        // implement serde
3659        assert_eq!("c3", final_hash_agg.input_schema().field(2).name());
3660
3661        Ok(())
3662    }
3663
3664    #[tokio::test]
3665    async fn hash_agg_group_by_partitioned() -> Result<()> {
3666        let logical_plan = test_csv_scan()
3667            .await?
3668            .aggregate(vec![col("c1")], vec![sum(col("c2"))])?
3669            .build()?;
3670
3671        let execution_plan = plan(&logical_plan).await?;
3672        let formatted = format!("{execution_plan:?}");
3673
3674        // Make sure the plan contains a FinalPartitioned, which means it will not use the Final
3675        // mode in Aggregate (which is slower)
3676        assert!(formatted.contains("FinalPartitioned"));
3677
3678        Ok(())
3679    }
3680
3681    #[tokio::test]
3682    async fn hash_agg_group_by_partitioned_on_dicts() -> Result<()> {
3683        let dict_array: DictionaryArray<Int32Type> =
3684            vec!["A", "B", "A", "A", "C", "A"].into_iter().collect();
3685        let val_array: Int32Array = vec![1, 2, 2, 4, 1, 1].into();
3686
3687        let batch = RecordBatch::try_from_iter(vec![
3688            ("d1", Arc::new(dict_array) as ArrayRef),
3689            ("d2", Arc::new(val_array) as ArrayRef),
3690        ])
3691        .unwrap();
3692
3693        let table = MemTable::try_new(batch.schema(), vec![vec![batch]])?;
3694        let ctx = SessionContext::new();
3695
3696        let logical_plan = LogicalPlanBuilder::from(
3697            ctx.read_table(Arc::new(table))?.into_optimized_plan()?,
3698        )
3699        .aggregate(vec![col("d1")], vec![sum(col("d2"))])?
3700        .build()?;
3701
3702        let execution_plan = plan(&logical_plan).await?;
3703        let formatted = format!("{execution_plan:?}");
3704
3705        // Make sure the plan contains a FinalPartitioned, which means it will not use the Final
3706        // mode in Aggregate (which is slower)
3707        assert!(formatted.contains("FinalPartitioned"));
3708        Ok(())
3709    }
3710
3711    #[tokio::test]
3712    async fn hash_agg_grouping_set_by_partitioned() -> Result<()> {
3713        let grouping_set_expr = Expr::GroupingSet(GroupingSet::GroupingSets(vec![
3714            vec![col("c1")],
3715            vec![col("c2")],
3716            vec![col("c1"), col("c2")],
3717        ]));
3718        let logical_plan = test_csv_scan()
3719            .await?
3720            .aggregate(vec![grouping_set_expr], vec![sum(col("c3"))])?
3721            .build()?;
3722
3723        let execution_plan = plan(&logical_plan).await?;
3724        let formatted = format!("{execution_plan:?}");
3725
3726        // Make sure the plan contains a FinalPartitioned, which means it will not use the Final
3727        // mode in Aggregate (which is slower)
3728        assert!(formatted.contains("FinalPartitioned"));
3729
3730        Ok(())
3731    }
3732
3733    #[tokio::test]
3734    async fn aggregate_with_alias() -> Result<()> {
3735        let schema = Arc::new(Schema::new(vec![
3736            Field::new("c1", DataType::Utf8, false),
3737            Field::new("c2", DataType::UInt32, false),
3738        ]));
3739
3740        let logical_plan = scan_empty(None, schema.as_ref(), None)?
3741            .aggregate(vec![col("c1")], vec![sum(col("c2"))])?
3742            .project(vec![col("c1"), sum(col("c2")).alias("total_salary")])?
3743            .build()?;
3744
3745        let physical_plan = plan(&logical_plan).await?;
3746        assert_eq!("c1", physical_plan.schema().field(0).name().as_str());
3747        assert_eq!(
3748            "total_salary",
3749            physical_plan.schema().field(1).name().as_str()
3750        );
3751        Ok(())
3752    }
3753
3754    #[tokio::test]
3755    async fn test_aggregate_count_all_with_alias() -> Result<()> {
3756        let schema = Arc::new(Schema::new(vec![
3757            Field::new("c1", DataType::Utf8, false),
3758            Field::new("c2", DataType::UInt32, false),
3759        ]));
3760
3761        let logical_plan = scan_empty(None, schema.as_ref(), None)?
3762            .aggregate(Vec::<Expr>::new(), vec![count_all().alias("total_rows")])?
3763            .build()?;
3764
3765        let physical_plan = plan(&logical_plan).await?;
3766        assert_eq!(
3767            "total_rows",
3768            physical_plan.schema().field(0).name().as_str()
3769        );
3770        Ok(())
3771    }
3772
3773    #[tokio::test]
3774    async fn test_explain() {
3775        let schema = Schema::new(vec![Field::new("id", DataType::Int32, false)]);
3776
3777        let logical_plan = scan_empty(Some("employee"), &schema, None)
3778            .unwrap()
3779            .explain(true, false)
3780            .unwrap()
3781            .build()
3782            .unwrap();
3783
3784        let plan = plan(&logical_plan).await.unwrap();
3785        if let Some(plan) = plan.as_any().downcast_ref::<ExplainExec>() {
3786            let stringified_plans = plan.stringified_plans();
3787            assert!(stringified_plans.len() >= 4);
3788            assert!(
3789                stringified_plans
3790                    .iter()
3791                    .any(|p| p.plan_type == PlanType::FinalLogicalPlan)
3792            );
3793            assert!(
3794                stringified_plans
3795                    .iter()
3796                    .any(|p| p.plan_type == PlanType::InitialPhysicalPlan)
3797            );
3798            assert!(
3799                stringified_plans.iter().any(|p| matches!(
3800                    p.plan_type,
3801                    PlanType::OptimizedPhysicalPlan { .. }
3802                ))
3803            );
3804            assert!(
3805                stringified_plans
3806                    .iter()
3807                    .any(|p| p.plan_type == PlanType::FinalPhysicalPlan)
3808            );
3809        } else {
3810            panic!(
3811                "Plan was not an explain plan: {}",
3812                displayable(plan.as_ref()).indent(true)
3813            );
3814        }
3815    }
3816
3817    #[tokio::test]
3818    async fn test_explain_indent_err() {
3819        let planner = DefaultPhysicalPlanner::default();
3820        let ctx = SessionContext::new();
3821        let schema = Schema::new(vec![Field::new("id", DataType::Int32, false)]);
3822        let plan = Arc::new(
3823            scan_empty(Some("employee"), &schema, None)
3824                .unwrap()
3825                .explain(true, false)
3826                .unwrap()
3827                .build()
3828                .unwrap(),
3829        );
3830
3831        // Create a schema
3832        let schema = Arc::new(Schema::new(vec![
3833            Field::new("plan_type", DataType::Utf8, false),
3834            Field::new("plan", DataType::Utf8, false),
3835        ]));
3836
3837        // Create invalid indentation in the plan
3838        let stringified_plans =
3839            vec![StringifiedPlan::new(PlanType::FinalLogicalPlan, "Test Err")];
3840
3841        let explain = Explain {
3842            verbose: false,
3843            explain_format: ExplainFormat::Indent,
3844            plan,
3845            stringified_plans,
3846            schema: schema.to_dfschema_ref().unwrap(),
3847            logical_optimization_succeeded: false,
3848        };
3849        let plan = planner
3850            .handle_explain(&explain, &ctx.state())
3851            .await
3852            .unwrap();
3853        if let Some(plan) = plan.as_any().downcast_ref::<ExplainExec>() {
3854            let stringified_plans = plan.stringified_plans();
3855            assert_eq!(stringified_plans.len(), 1);
3856            assert_eq!(stringified_plans[0].plan.as_str(), "Test Err");
3857        } else {
3858            panic!(
3859                "Plan was not an explain plan: {}",
3860                displayable(plan.as_ref()).indent(true)
3861            );
3862        }
3863    }
3864
3865    struct ErrorExtensionPlanner {}
3866
3867    #[async_trait]
3868    impl ExtensionPlanner for ErrorExtensionPlanner {
3869        /// Create a physical plan for an extension node
3870        async fn plan_extension(
3871            &self,
3872            _planner: &dyn PhysicalPlanner,
3873            _node: &dyn UserDefinedLogicalNode,
3874            _logical_inputs: &[&LogicalPlan],
3875            _physical_inputs: &[Arc<dyn ExecutionPlan>],
3876            _session_state: &SessionState,
3877        ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
3878            internal_err!("BOOM")
3879        }
3880    }
3881    /// An example extension node that doesn't do anything
3882    #[derive(PartialEq, Eq, Hash)]
3883    struct NoOpExtensionNode {
3884        schema: DFSchemaRef,
3885    }
3886
3887    impl Default for NoOpExtensionNode {
3888        fn default() -> Self {
3889            Self {
3890                schema: DFSchemaRef::new(
3891                    DFSchema::from_unqualified_fields(
3892                        vec![Field::new("a", DataType::Int32, false)].into(),
3893                        HashMap::new(),
3894                    )
3895                    .unwrap(),
3896                ),
3897            }
3898        }
3899    }
3900
3901    impl Debug for NoOpExtensionNode {
3902        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3903            write!(f, "NoOp")
3904        }
3905    }
3906
3907    // Implementation needed for `UserDefinedLogicalNodeCore`, since the only field is
3908    // a schema, we can't derive `PartialOrd`, and we can't compare these.
3909    impl PartialOrd for NoOpExtensionNode {
3910        fn partial_cmp(&self, _other: &Self) -> Option<Ordering> {
3911            None
3912        }
3913    }
3914
3915    impl UserDefinedLogicalNodeCore for NoOpExtensionNode {
3916        fn name(&self) -> &str {
3917            "NoOp"
3918        }
3919
3920        fn inputs(&self) -> Vec<&LogicalPlan> {
3921            vec![]
3922        }
3923
3924        fn schema(&self) -> &DFSchemaRef {
3925            &self.schema
3926        }
3927
3928        fn expressions(&self) -> Vec<Expr> {
3929            vec![]
3930        }
3931
3932        fn fmt_for_explain(&self, f: &mut fmt::Formatter) -> fmt::Result {
3933            write!(f, "NoOp")
3934        }
3935
3936        fn with_exprs_and_inputs(
3937            &self,
3938            _exprs: Vec<Expr>,
3939            _inputs: Vec<LogicalPlan>,
3940        ) -> Result<Self> {
3941            unimplemented!("NoOp");
3942        }
3943
3944        fn supports_limit_pushdown(&self) -> bool {
3945            false // Disallow limit push-down by default
3946        }
3947    }
3948
3949    #[derive(Debug)]
3950    struct NoOpExecutionPlan {
3951        cache: Arc<PlanProperties>,
3952    }
3953
3954    impl NoOpExecutionPlan {
3955        fn new(schema: SchemaRef) -> Self {
3956            let cache = Self::compute_properties(schema);
3957            Self {
3958                cache: Arc::new(cache),
3959            }
3960        }
3961
3962        /// This function creates the cache object that stores the plan properties such as schema, equivalence properties, ordering, partitioning, etc.
3963        fn compute_properties(schema: SchemaRef) -> PlanProperties {
3964            PlanProperties::new(
3965                EquivalenceProperties::new(schema),
3966                Partitioning::UnknownPartitioning(1),
3967                EmissionType::Incremental,
3968                Boundedness::Bounded,
3969            )
3970        }
3971    }
3972
3973    impl DisplayAs for NoOpExecutionPlan {
3974        fn fmt_as(&self, t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
3975            match t {
3976                DisplayFormatType::Default | DisplayFormatType::Verbose => {
3977                    write!(f, "NoOpExecutionPlan")
3978                }
3979                DisplayFormatType::TreeRender => {
3980                    // TODO: collect info
3981                    write!(f, "")
3982                }
3983            }
3984        }
3985    }
3986
3987    impl ExecutionPlan for NoOpExecutionPlan {
3988        fn name(&self) -> &'static str {
3989            "NoOpExecutionPlan"
3990        }
3991
3992        /// Return a reference to Any that can be used for downcasting
3993        fn as_any(&self) -> &dyn Any {
3994            self
3995        }
3996
3997        fn properties(&self) -> &Arc<PlanProperties> {
3998            &self.cache
3999        }
4000
4001        fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
4002            vec![]
4003        }
4004
4005        fn with_new_children(
4006            self: Arc<Self>,
4007            _children: Vec<Arc<dyn ExecutionPlan>>,
4008        ) -> Result<Arc<dyn ExecutionPlan>> {
4009            unimplemented!("NoOpExecutionPlan::with_new_children");
4010        }
4011
4012        fn execute(
4013            &self,
4014            _partition: usize,
4015            _context: Arc<TaskContext>,
4016        ) -> Result<SendableRecordBatchStream> {
4017            unimplemented!("NoOpExecutionPlan::execute");
4018        }
4019    }
4020
4021    //  Produces an execution plan where the schema is mismatched from
4022    //  the logical plan node.
4023    struct BadExtensionPlanner {}
4024
4025    #[async_trait]
4026    impl ExtensionPlanner for BadExtensionPlanner {
4027        /// Create a physical plan for an extension node
4028        async fn plan_extension(
4029            &self,
4030            _planner: &dyn PhysicalPlanner,
4031            _node: &dyn UserDefinedLogicalNode,
4032            _logical_inputs: &[&LogicalPlan],
4033            _physical_inputs: &[Arc<dyn ExecutionPlan>],
4034            _session_state: &SessionState,
4035        ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
4036            Ok(Some(Arc::new(NoOpExecutionPlan::new(SchemaRef::new(
4037                Schema::new(vec![Field::new("b", DataType::Int32, false)]),
4038            )))))
4039        }
4040    }
4041
4042    async fn test_csv_scan_with_name(name: &str) -> Result<LogicalPlanBuilder> {
4043        let ctx = SessionContext::new();
4044        let testdata = crate::test_util::arrow_test_data();
4045        let path = format!("{testdata}/csv/aggregate_test_100.csv");
4046        let options = CsvReadOptions::new().schema_infer_max_records(100);
4047        let logical_plan =
4048            match ctx.read_csv(path, options).await?.into_optimized_plan()? {
4049                LogicalPlan::TableScan(ref scan) => {
4050                    let mut scan = scan.clone();
4051                    let table_reference = TableReference::from(name);
4052                    scan.table_name = table_reference;
4053                    let new_schema = scan
4054                        .projected_schema
4055                        .as_ref()
4056                        .clone()
4057                        .replace_qualifier(name.to_string());
4058                    scan.projected_schema = Arc::new(new_schema);
4059                    LogicalPlan::TableScan(scan)
4060                }
4061                _ => unimplemented!(),
4062            };
4063        Ok(LogicalPlanBuilder::from(logical_plan))
4064    }
4065
4066    async fn test_csv_scan() -> Result<LogicalPlanBuilder> {
4067        let ctx = SessionContext::new();
4068        let testdata = crate::test_util::arrow_test_data();
4069        let path = format!("{testdata}/csv/aggregate_test_100.csv");
4070        let options = CsvReadOptions::new().schema_infer_max_records(100);
4071        Ok(LogicalPlanBuilder::from(
4072            ctx.read_csv(path, options).await?.into_optimized_plan()?,
4073        ))
4074    }
4075
4076    #[tokio::test]
4077    async fn test_display_plan_in_graphviz_format() {
4078        let schema = Schema::new(vec![Field::new("id", DataType::Int32, false)]);
4079
4080        let logical_plan = scan_empty(Some("employee"), &schema, None)
4081            .unwrap()
4082            .project(vec![col("id") + lit(2)])
4083            .unwrap()
4084            .build()
4085            .unwrap();
4086
4087        let plan = plan(&logical_plan).await.unwrap();
4088
4089        let expected_graph = r#"
4090// Begin DataFusion GraphViz Plan,
4091// display it online here: https://dreampuf.github.io/GraphvizOnline
4092
4093digraph {
4094    1[shape=box label="ProjectionExec: expr=[id@0 + 2 as employee.id + Int32(2)]", tooltip=""]
4095    2[shape=box label="EmptyExec", tooltip=""]
4096    1 -> 2 [arrowhead=none, arrowtail=normal, dir=back]
4097}
4098// End DataFusion GraphViz Plan
4099"#;
4100
4101        let generated_graph = format!("{}", displayable(&*plan).graphviz());
4102
4103        assert_eq!(expected_graph, generated_graph);
4104    }
4105
4106    #[tokio::test]
4107    async fn test_display_graphviz_with_statistics() {
4108        let schema = Schema::new(vec![Field::new("id", DataType::Int32, false)]);
4109
4110        let logical_plan = scan_empty(Some("employee"), &schema, None)
4111            .unwrap()
4112            .project(vec![col("id") + lit(2)])
4113            .unwrap()
4114            .build()
4115            .unwrap();
4116
4117        let plan = plan(&logical_plan).await.unwrap();
4118
4119        let expected_tooltip = ", tooltip=\"statistics=[";
4120
4121        let generated_graph = format!(
4122            "{}",
4123            displayable(&*plan).set_show_statistics(true).graphviz()
4124        );
4125
4126        assert_contains!(generated_graph, expected_tooltip);
4127    }
4128
4129    /// Extension Node which passes invariant checks
4130    #[derive(Debug)]
4131    struct OkExtensionNode(Vec<Arc<dyn ExecutionPlan>>);
4132    impl ExecutionPlan for OkExtensionNode {
4133        fn name(&self) -> &str {
4134            "always ok"
4135        }
4136        fn with_new_children(
4137            self: Arc<Self>,
4138            children: Vec<Arc<dyn ExecutionPlan>>,
4139        ) -> Result<Arc<dyn ExecutionPlan>> {
4140            Ok(Arc::new(Self(children)))
4141        }
4142        fn schema(&self) -> SchemaRef {
4143            Arc::new(Schema::empty())
4144        }
4145        fn as_any(&self) -> &dyn Any {
4146            unimplemented!()
4147        }
4148        fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
4149            self.0.iter().collect::<Vec<_>>()
4150        }
4151        fn properties(&self) -> &Arc<PlanProperties> {
4152            unimplemented!()
4153        }
4154        fn execute(
4155            &self,
4156            _partition: usize,
4157            _context: Arc<TaskContext>,
4158        ) -> Result<SendableRecordBatchStream> {
4159            unimplemented!()
4160        }
4161    }
4162    impl DisplayAs for OkExtensionNode {
4163        fn fmt_as(&self, _t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
4164            write!(f, "{}", self.name())
4165        }
4166    }
4167
4168    /// Extension Node which fails the [`OptimizationInvariantChecker`].
4169    #[derive(Debug)]
4170    struct InvariantFailsExtensionNode;
4171    impl ExecutionPlan for InvariantFailsExtensionNode {
4172        fn name(&self) -> &str {
4173            "InvariantFailsExtensionNode"
4174        }
4175        fn check_invariants(&self, check: InvariantLevel) -> Result<()> {
4176            match check {
4177                InvariantLevel::Always => plan_err!(
4178                    "extension node failed it's user-defined always-invariant check"
4179                ),
4180                InvariantLevel::Executable => panic!(
4181                    "the OptimizationInvariantChecker should not be checking for executableness"
4182                ),
4183            }
4184        }
4185        fn schema(&self) -> SchemaRef {
4186            Arc::new(Schema::empty())
4187        }
4188        fn with_new_children(
4189            self: Arc<Self>,
4190            _children: Vec<Arc<dyn ExecutionPlan>>,
4191        ) -> Result<Arc<dyn ExecutionPlan>> {
4192            unimplemented!()
4193        }
4194        fn as_any(&self) -> &dyn Any {
4195            unimplemented!()
4196        }
4197        fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
4198            unimplemented!()
4199        }
4200        fn properties(&self) -> &Arc<PlanProperties> {
4201            unimplemented!()
4202        }
4203        fn execute(
4204            &self,
4205            _partition: usize,
4206            _context: Arc<TaskContext>,
4207        ) -> Result<SendableRecordBatchStream> {
4208            unimplemented!()
4209        }
4210    }
4211    impl DisplayAs for InvariantFailsExtensionNode {
4212        fn fmt_as(&self, _t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
4213            write!(f, "{}", self.name())
4214        }
4215    }
4216
4217    /// Extension Optimizer rule that requires the schema check
4218    #[derive(Debug)]
4219    struct OptimizerRuleWithSchemaCheck;
4220    impl PhysicalOptimizerRule for OptimizerRuleWithSchemaCheck {
4221        fn optimize(
4222            &self,
4223            plan: Arc<dyn ExecutionPlan>,
4224            _config: &ConfigOptions,
4225        ) -> Result<Arc<dyn ExecutionPlan>> {
4226            Ok(plan)
4227        }
4228        fn name(&self) -> &str {
4229            "OptimizerRuleWithSchemaCheck"
4230        }
4231        fn schema_check(&self) -> bool {
4232            true
4233        }
4234    }
4235
4236    #[test]
4237    fn test_optimization_invariant_checker() -> Result<()> {
4238        let rule: Arc<dyn PhysicalOptimizerRule + Send + Sync> =
4239            Arc::new(OptimizerRuleWithSchemaCheck);
4240
4241        // ok plan
4242        let ok_node: Arc<dyn ExecutionPlan> = Arc::new(OkExtensionNode(vec![]));
4243        let child = Arc::clone(&ok_node);
4244        let ok_plan = Arc::clone(&ok_node).with_new_children(vec![
4245            Arc::clone(&child).with_new_children(vec![Arc::clone(&child)])?,
4246            Arc::clone(&child),
4247        ])?;
4248
4249        // Test: check should pass with same schema
4250        let equal_schema = ok_plan.schema();
4251        OptimizationInvariantChecker::new(&rule).check(&ok_plan, &equal_schema)?;
4252
4253        // Test: should fail with schema changed
4254        let different_schema =
4255            Arc::new(Schema::new(vec![Field::new("a", DataType::Boolean, false)]));
4256        let expected_err = OptimizationInvariantChecker::new(&rule)
4257            .check(&ok_plan, &different_schema)
4258            .unwrap_err();
4259        assert!(expected_err.to_string().contains("PhysicalOptimizer rule 'OptimizerRuleWithSchemaCheck' failed. Schema mismatch. Expected original schema"));
4260
4261        // Test: should fail when extension node fails it's own invariant check
4262        let failing_node: Arc<dyn ExecutionPlan> = Arc::new(InvariantFailsExtensionNode);
4263        let expected_err = OptimizationInvariantChecker::new(&rule)
4264            .check(&failing_node, &ok_plan.schema())
4265            .unwrap_err();
4266        assert!(
4267            expected_err.to_string().contains(
4268                "extension node failed it's user-defined always-invariant check"
4269            )
4270        );
4271
4272        // Test: should fail when descendent extension node fails
4273        let failing_node: Arc<dyn ExecutionPlan> = Arc::new(InvariantFailsExtensionNode);
4274        let invalid_plan = ok_node.with_new_children(vec![
4275            Arc::clone(&child).with_new_children(vec![Arc::clone(&failing_node)])?,
4276            Arc::clone(&child),
4277        ])?;
4278        let expected_err = OptimizationInvariantChecker::new(&rule)
4279            .check(&invalid_plan, &ok_plan.schema())
4280            .unwrap_err();
4281        assert!(
4282            expected_err.to_string().contains(
4283                "extension node failed it's user-defined always-invariant check"
4284            )
4285        );
4286
4287        Ok(())
4288    }
4289
4290    /// Extension Node which fails the [`InvariantChecker`]
4291    /// if, and only if, [`InvariantLevel::Executable`]
4292    #[derive(Debug)]
4293    struct ExecutableInvariantFails;
4294    impl ExecutionPlan for ExecutableInvariantFails {
4295        fn name(&self) -> &str {
4296            "ExecutableInvariantFails"
4297        }
4298        fn check_invariants(&self, check: InvariantLevel) -> Result<()> {
4299            match check {
4300                InvariantLevel::Always => Ok(()),
4301                InvariantLevel::Executable => plan_err!(
4302                    "extension node failed it's user-defined executable-invariant check"
4303                ),
4304            }
4305        }
4306        fn schema(&self) -> SchemaRef {
4307            Arc::new(Schema::empty())
4308        }
4309        fn with_new_children(
4310            self: Arc<Self>,
4311            _children: Vec<Arc<dyn ExecutionPlan>>,
4312        ) -> Result<Arc<dyn ExecutionPlan>> {
4313            unimplemented!()
4314        }
4315        fn as_any(&self) -> &dyn Any {
4316            unimplemented!()
4317        }
4318        fn children(&self) -> Vec<&Arc<dyn ExecutionPlan>> {
4319            vec![]
4320        }
4321        fn properties(&self) -> &Arc<PlanProperties> {
4322            unimplemented!()
4323        }
4324        fn execute(
4325            &self,
4326            _partition: usize,
4327            _context: Arc<TaskContext>,
4328        ) -> Result<SendableRecordBatchStream> {
4329            unimplemented!()
4330        }
4331    }
4332    impl DisplayAs for ExecutableInvariantFails {
4333        fn fmt_as(&self, _t: DisplayFormatType, f: &mut fmt::Formatter) -> fmt::Result {
4334            write!(f, "{}", self.name())
4335        }
4336    }
4337
4338    #[test]
4339    fn test_invariant_checker_levels() -> Result<()> {
4340        // plan that passes the always-invariant, but fails the executable check
4341        let plan: Arc<dyn ExecutionPlan> = Arc::new(ExecutableInvariantFails);
4342
4343        // Test: check should pass with less stringent Always check
4344        InvariantChecker(InvariantLevel::Always).check(&plan)?;
4345
4346        // Test: should fail the executable check
4347        let expected_err = InvariantChecker(InvariantLevel::Executable)
4348            .check(&plan)
4349            .unwrap_err();
4350        assert!(expected_err.to_string().contains(
4351            "extension node failed it's user-defined executable-invariant check"
4352        ));
4353
4354        // Test: should fail when descendent extension node fails
4355        let failing_node: Arc<dyn ExecutionPlan> = Arc::new(ExecutableInvariantFails);
4356        let ok_node: Arc<dyn ExecutionPlan> = Arc::new(OkExtensionNode(vec![]));
4357        let child = Arc::clone(&ok_node);
4358        let plan = ok_node.with_new_children(vec![
4359            Arc::clone(&child).with_new_children(vec![Arc::clone(&failing_node)])?,
4360            Arc::clone(&child),
4361        ])?;
4362        let expected_err = InvariantChecker(InvariantLevel::Executable)
4363            .check(&plan)
4364            .unwrap_err();
4365        assert!(expected_err.to_string().contains(
4366            "extension node failed it's user-defined executable-invariant check"
4367        ));
4368
4369        Ok(())
4370    }
4371
4372    // Reproducer for DataFusion issue #17405:
4373    //
4374    // The following SQL is semantically invalid. Notably, the `SELECT left_table.a, right_table.a`
4375    // clause is missing from the explicit logical plan:
4376    //
4377    // SELECT a FROM (
4378    //       -- SELECT left_table.a, right_table.a
4379    //       FROM left_table
4380    //       FULL JOIN right_table ON left_table.a = right_table.a
4381    // ) AS alias
4382    // GROUP BY a;
4383    //
4384    // As a result, the variables within `alias` subquery are not properly distinguished, which
4385    // leads to a bug for logical and physical planning.
4386    //
4387    // The fix is to implicitly insert a Projection node to represent the missing SELECT clause to
4388    // ensure each field is correctly aliased to a unique name when the SubqueryAlias node is added.
4389    #[tokio::test]
4390    async fn subquery_alias_confusing_the_optimizer() -> Result<()> {
4391        let state = make_session_state();
4392
4393        let schema = Schema::new(vec![Field::new("a", DataType::Int32, false)]);
4394        let schema = Arc::new(schema);
4395
4396        let table = MemTable::try_new(schema.clone(), vec![vec![]])?;
4397        let table = Arc::new(table);
4398
4399        let source = DefaultTableSource::new(table);
4400        let source = Arc::new(source);
4401
4402        let left = LogicalPlanBuilder::scan("left", source.clone(), None)?;
4403        let right = LogicalPlanBuilder::scan("right", source, None)?.build()?;
4404
4405        let join_keys = (
4406            vec![Column::new(Some("left"), "a")],
4407            vec![Column::new(Some("right"), "a")],
4408        );
4409
4410        let join = left.join(right, JoinType::Full, join_keys, None)?.build()?;
4411
4412        let alias = subquery_alias(join, "alias")?;
4413
4414        let planner = DefaultPhysicalPlanner::default();
4415
4416        let logical_plan = LogicalPlanBuilder::new(alias)
4417            .aggregate(vec![col("a:1")], Vec::<Expr>::new())?
4418            .build()?;
4419        let _physical_plan = planner.create_physical_plan(&logical_plan, &state).await?;
4420
4421        let optimized_logical_plan = state.optimize(&logical_plan)?;
4422        let _optimized_physical_plan = planner
4423            .create_physical_plan(&optimized_logical_plan, &state)
4424            .await?;
4425
4426        Ok(())
4427    }
4428
4429    // --- Tests for aggregate schema mismatch error messages ---
4430
4431    use crate::catalog::TableProvider;
4432    use datafusion_catalog::Session;
4433    use datafusion_expr::TableType;
4434
4435    /// A TableProvider that returns schemas for logical planning vs physical planning.
4436    /// Used to test schema mismatch error messages.
4437    #[derive(Debug)]
4438    struct MockSchemaTableProvider {
4439        logical_schema: SchemaRef,
4440        physical_schema: SchemaRef,
4441    }
4442
4443    #[async_trait]
4444    impl TableProvider for MockSchemaTableProvider {
4445        fn as_any(&self) -> &dyn Any {
4446            self
4447        }
4448
4449        fn schema(&self) -> SchemaRef {
4450            Arc::clone(&self.logical_schema)
4451        }
4452
4453        fn table_type(&self) -> TableType {
4454            TableType::Base
4455        }
4456
4457        async fn scan(
4458            &self,
4459            _state: &dyn Session,
4460            _projection: Option<&Vec<usize>>,
4461            _filters: &[Expr],
4462            _limit: Option<usize>,
4463        ) -> Result<Arc<dyn ExecutionPlan>> {
4464            Ok(Arc::new(NoOpExecutionPlan::new(Arc::clone(
4465                &self.physical_schema,
4466            ))))
4467        }
4468    }
4469
4470    /// Attempts to plan a query with potentially mismatched schemas.
4471    async fn plan_with_schemas(
4472        logical_schema: SchemaRef,
4473        physical_schema: SchemaRef,
4474        query: &str,
4475    ) -> Result<Arc<dyn ExecutionPlan>> {
4476        let provider = MockSchemaTableProvider {
4477            logical_schema,
4478            physical_schema,
4479        };
4480        let ctx = SessionContext::new();
4481        ctx.register_table("test", Arc::new(provider)).unwrap();
4482
4483        ctx.sql(query).await.unwrap().create_physical_plan().await
4484    }
4485
4486    #[tokio::test]
4487    // When schemas match, planning proceeds past the schema_satisfied_by check.
4488    // It then panics on unimplemented error in NoOpExecutionPlan.
4489    #[should_panic(expected = "NoOpExecutionPlan")]
4490    async fn test_aggregate_schema_check_passes() {
4491        let schema =
4492            Arc::new(Schema::new(vec![Field::new("c1", DataType::Int32, false)]));
4493
4494        plan_with_schemas(
4495            Arc::clone(&schema),
4496            schema,
4497            "SELECT count(*) FROM test GROUP BY c1",
4498        )
4499        .await
4500        .unwrap();
4501    }
4502
4503    #[tokio::test]
4504    async fn test_aggregate_schema_mismatch_metadata() {
4505        let logical_schema =
4506            Arc::new(Schema::new(vec![Field::new("c1", DataType::Int32, false)]));
4507        let physical_schema = Arc::new(
4508            Schema::new(vec![Field::new("c1", DataType::Int32, false)])
4509                .with_metadata(HashMap::from([("key".into(), "value".into())])),
4510        );
4511
4512        let err = plan_with_schemas(
4513            logical_schema,
4514            physical_schema,
4515            "SELECT count(*) FROM test GROUP BY c1",
4516        )
4517        .await
4518        .unwrap_err();
4519
4520        assert_contains!(err.to_string(), "schema metadata differs");
4521    }
4522
4523    #[tokio::test]
4524    async fn test_aggregate_schema_mismatch_field_count() {
4525        let logical_schema =
4526            Arc::new(Schema::new(vec![Field::new("c1", DataType::Int32, false)]));
4527        let physical_schema = Arc::new(Schema::new(vec![
4528            Field::new("c1", DataType::Int32, false),
4529            Field::new("c2", DataType::Int32, false),
4530        ]));
4531
4532        let err = plan_with_schemas(
4533            logical_schema,
4534            physical_schema,
4535            "SELECT count(*) FROM test GROUP BY c1",
4536        )
4537        .await
4538        .unwrap_err();
4539
4540        assert_contains!(err.to_string(), "Different number of fields");
4541    }
4542
4543    #[tokio::test]
4544    async fn test_aggregate_schema_mismatch_field_name() {
4545        let logical_schema =
4546            Arc::new(Schema::new(vec![Field::new("c1", DataType::Int32, false)]));
4547        let physical_schema = Arc::new(Schema::new(vec![Field::new(
4548            "different_name",
4549            DataType::Int32,
4550            false,
4551        )]));
4552
4553        let err = plan_with_schemas(
4554            logical_schema,
4555            physical_schema,
4556            "SELECT count(*) FROM test GROUP BY c1",
4557        )
4558        .await
4559        .unwrap_err();
4560
4561        assert_contains!(err.to_string(), "field name at index");
4562    }
4563
4564    #[tokio::test]
4565    async fn test_aggregate_schema_mismatch_field_type() {
4566        let logical_schema =
4567            Arc::new(Schema::new(vec![Field::new("c1", DataType::Int32, false)]));
4568        let physical_schema =
4569            Arc::new(Schema::new(vec![Field::new("c1", DataType::Int64, false)]));
4570
4571        let err = plan_with_schemas(
4572            logical_schema,
4573            physical_schema,
4574            "SELECT count(*) FROM test GROUP BY c1",
4575        )
4576        .await
4577        .unwrap_err();
4578
4579        assert_contains!(err.to_string(), "field data type at index");
4580    }
4581
4582    #[tokio::test]
4583    async fn test_aggregate_schema_mismatch_field_nullability() {
4584        let logical_schema =
4585            Arc::new(Schema::new(vec![Field::new("c1", DataType::Int32, false)]));
4586        let physical_schema =
4587            Arc::new(Schema::new(vec![Field::new("c1", DataType::Int32, true)]));
4588
4589        let err = plan_with_schemas(
4590            logical_schema,
4591            physical_schema,
4592            "SELECT count(*) FROM test GROUP BY c1",
4593        )
4594        .await
4595        .unwrap_err();
4596
4597        assert_contains!(err.to_string(), "field nullability at index");
4598    }
4599
4600    #[tokio::test]
4601    async fn test_aggregate_schema_mismatch_field_metadata() {
4602        let logical_schema =
4603            Arc::new(Schema::new(vec![Field::new("c1", DataType::Int32, false)]));
4604        let physical_schema = Arc::new(Schema::new(vec![
4605            Field::new("c1", DataType::Int32, false)
4606                .with_metadata(HashMap::from([("key".into(), "value".into())])),
4607        ]));
4608
4609        let err = plan_with_schemas(
4610            logical_schema,
4611            physical_schema,
4612            "SELECT count(*) FROM test GROUP BY c1",
4613        )
4614        .await
4615        .unwrap_err();
4616
4617        assert_contains!(err.to_string(), "field metadata at index");
4618    }
4619
4620    #[tokio::test]
4621    async fn test_aggregate_schema_mismatch_multiple() {
4622        let logical_schema = Arc::new(Schema::new(vec![
4623            Field::new("c1", DataType::Int32, false),
4624            Field::new("c2", DataType::Utf8, false),
4625        ]));
4626        let physical_schema = Arc::new(
4627            Schema::new(vec![
4628                Field::new("c1", DataType::Int64, true)
4629                    .with_metadata(HashMap::from([("key".into(), "value".into())])),
4630                Field::new("c2", DataType::Utf8, false),
4631            ])
4632            .with_metadata(HashMap::from([(
4633                "schema_key".into(),
4634                "schema_value".into(),
4635            )])),
4636        );
4637
4638        let err = plan_with_schemas(
4639            logical_schema,
4640            physical_schema,
4641            "SELECT count(*) FROM test GROUP BY c1",
4642        )
4643        .await
4644        .unwrap_err();
4645
4646        // Verify all applicable error fragments are present
4647        let err_str = err.to_string();
4648        assert_contains!(&err_str, "schema metadata differs");
4649        assert_contains!(&err_str, "field data type at index");
4650        assert_contains!(&err_str, "field nullability at index");
4651        assert_contains!(&err_str, "field metadata at index");
4652    }
4653
4654    #[derive(Debug)]
4655    struct MockTableSource {
4656        schema: SchemaRef,
4657    }
4658
4659    impl TableSource for MockTableSource {
4660        fn as_any(&self) -> &dyn Any {
4661            self
4662        }
4663
4664        fn schema(&self) -> SchemaRef {
4665            Arc::clone(&self.schema)
4666        }
4667    }
4668
4669    struct MockTableScanExtensionPlanner;
4670
4671    #[async_trait]
4672    impl ExtensionPlanner for MockTableScanExtensionPlanner {
4673        async fn plan_extension(
4674            &self,
4675            _planner: &dyn PhysicalPlanner,
4676            _node: &dyn UserDefinedLogicalNode,
4677            _logical_inputs: &[&LogicalPlan],
4678            _physical_inputs: &[Arc<dyn ExecutionPlan>],
4679            _session_state: &SessionState,
4680        ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
4681            Ok(None)
4682        }
4683
4684        async fn plan_table_scan(
4685            &self,
4686            _planner: &dyn PhysicalPlanner,
4687            scan: &TableScan,
4688            _session_state: &SessionState,
4689        ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
4690            if scan.source.as_any().is::<MockTableSource>() {
4691                Ok(Some(Arc::new(EmptyExec::new(Arc::clone(
4692                    scan.projected_schema.inner(),
4693                )))))
4694            } else {
4695                Ok(None)
4696            }
4697        }
4698    }
4699
4700    #[tokio::test]
4701    async fn test_table_scan_extension_planner() {
4702        let session_state = make_session_state();
4703        let planner = Arc::new(MockTableScanExtensionPlanner);
4704        let physical_planner =
4705            DefaultPhysicalPlanner::with_extension_planners(vec![planner]);
4706
4707        let schema = Arc::new(Schema::new(vec![Field::new("a", DataType::Int32, false)]));
4708
4709        let table_source = Arc::new(MockTableSource {
4710            schema: Arc::clone(&schema),
4711        });
4712        let logical_plan = LogicalPlanBuilder::scan("test", table_source, None)
4713            .unwrap()
4714            .build()
4715            .unwrap();
4716
4717        let plan = physical_planner
4718            .create_physical_plan(&logical_plan, &session_state)
4719            .await
4720            .unwrap();
4721
4722        assert_eq!(plan.schema(), schema);
4723        assert!(plan.as_any().is::<EmptyExec>());
4724    }
4725}