Skip to main content

datafusion_session/
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//! Query planner interfaces.
19
20use std::any::Any;
21use std::fmt::Debug;
22use std::sync::Arc;
23
24use async_trait::async_trait;
25use datafusion_common::{DFSchema, Result, not_impl_err};
26use datafusion_expr::physical_planning_context::PhysicalPlanningContext;
27use datafusion_expr::{Expr, LogicalPlan, TableScan, UserDefinedLogicalNode};
28use datafusion_physical_plan::{ExecutionPlan, PhysicalExpr};
29
30use crate::Session;
31
32/// A planner that creates a physical plan for a query.
33#[async_trait]
34pub trait QueryPlanner: Any + Debug {
35    /// Given a [`LogicalPlan`], create an [`ExecutionPlan`] suitable for execution
36    async fn create_physical_plan(
37        &self,
38        logical_plan: &LogicalPlan,
39        session: &dyn Session,
40    ) -> Result<Arc<dyn ExecutionPlan>>;
41}
42
43/// A query planner that reports that planning is not implemented.
44///
45/// [`Session`] implementations that do not expose a query planner can return
46/// this planner explicitly.
47#[derive(Debug, Default)]
48pub struct UnsupportedQueryPlanner;
49
50#[async_trait]
51impl QueryPlanner for UnsupportedQueryPlanner {
52    async fn create_physical_plan(
53        &self,
54        _logical_plan: &LogicalPlan,
55        _session: &dyn Session,
56    ) -> Result<Arc<dyn ExecutionPlan>> {
57        not_impl_err!("This session does not expose its query planner")
58    }
59}
60
61/// Physical query planner that converts a [`LogicalPlan`] to an
62/// [`ExecutionPlan`] suitable for execution.
63#[async_trait]
64pub trait PhysicalPlanner: Send + Sync {
65    /// Create a physical plan from a logical plan
66    async fn create_physical_plan(
67        &self,
68        logical_plan: &LogicalPlan,
69        session: &dyn Session,
70    ) -> Result<Arc<dyn ExecutionPlan>>;
71
72    /// Create a physical expression from a logical expression
73    /// suitable for evaluation
74    ///
75    /// `expr`: the expression to convert
76    ///
77    /// `input_dfschema`: the logical plan schema for evaluating `expr`
78    ///
79    /// `planning_ctx`: the [`PhysicalPlanningContext`] used to resolve
80    /// `Expr::ScalarSubquery` nodes. During physical planning the planner
81    /// threads the context of the plan currently being converted to a physical
82    /// plan (for example into [`ExtensionPlanner::plan_extension`], which
83    /// should forward it here). Callers creating physical expressions outside
84    /// of a plan should pass `&PhysicalPlanningContext::default()`.
85    fn create_physical_expr(
86        &self,
87        expr: &Expr,
88        input_dfschema: &DFSchema,
89        session: &dyn Session,
90        planning_ctx: &PhysicalPlanningContext,
91    ) -> Result<Arc<dyn PhysicalExpr>>;
92}
93
94/// This trait exposes the ability to plan an [`ExecutionPlan`] out of a [`LogicalPlan`].
95#[async_trait]
96pub trait ExtensionPlanner {
97    /// Create a physical plan for a [`UserDefinedLogicalNode`].
98    ///
99    /// `input_dfschema`: the logical plan schema for the inputs to this node
100    ///
101    /// Returns an error when the planner knows how to plan the concrete
102    /// implementation of `node` but errors while doing so.
103    ///
104    /// Returns `None` when the planner does not know how to plan the
105    /// `node` and wants to delegate the planning to another
106    /// [`ExtensionPlanner`].
107    ///
108    /// `planning_ctx` is the [`PhysicalPlanningContext`] of the plan subtree
109    /// currently being converted to a physical plan. Forward it to
110    /// [`PhysicalPlanner::create_physical_expr`] when creating this node's
111    /// physical expressions so that scalar subqueries resolve against the same
112    /// subquery state as the rest of the plan.
113    async fn plan_extension(
114        &self,
115        planner: &dyn PhysicalPlanner,
116        node: &dyn UserDefinedLogicalNode,
117        logical_inputs: &[&LogicalPlan],
118        physical_inputs: &[Arc<dyn ExecutionPlan>],
119        session: &dyn Session,
120        planning_ctx: &PhysicalPlanningContext,
121    ) -> Result<Option<Arc<dyn ExecutionPlan>>>;
122
123    /// Create a physical plan for a [`LogicalPlan::TableScan`].
124    ///
125    /// This is useful for planning valid [`TableSource`]s that are not `TableProvider`s.
126    ///
127    /// Returns:
128    /// * `Ok(Some(plan))` if the planner knows how to plan the `scan`
129    /// * `Ok(None)` if the planner does not know how to plan the `scan` and wants to delegate the planning to another [`ExtensionPlanner`]
130    /// * `Err` if the planner knows how to plan the `scan` but errors while doing so
131    ///
132    /// # Example
133    ///
134    /// ```rust,ignore
135    /// use std::sync::Arc;
136    /// use datafusion::physical_plan::ExecutionPlan;
137    /// use datafusion::logical_expr::TableScan;
138    /// use datafusion::catalog::Session;
139    /// use datafusion::error::Result;
140    /// use datafusion_session::{ExtensionPlanner, PhysicalPlanner};
141    /// use async_trait::async_trait;
142    ///
143    /// // Your custom table source type
144    /// struct MyCustomTableSource { /* ... */ }
145    ///
146    /// // Your custom execution plan
147    /// struct MyCustomExec { /* ... */ }
148    ///
149    /// struct MyExtensionPlanner;
150    ///
151    /// #[async_trait]
152    /// impl ExtensionPlanner for MyExtensionPlanner {
153    ///     async fn plan_extension(
154    ///         &self,
155    ///         _planner: &dyn PhysicalPlanner,
156    ///         _node: &dyn UserDefinedLogicalNode,
157    ///         _logical_inputs: &[&LogicalPlan],
158    ///         _physical_inputs: &[Arc<dyn ExecutionPlan>],
159    ///         _session: &dyn Session,
160    ///         _planning_ctx: &PhysicalPlanningContext,
161    ///     ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
162    ///         Ok(None)
163    ///     }
164    ///
165    ///     async fn plan_table_scan(
166    ///         &self,
167    ///         _planner: &dyn PhysicalPlanner,
168    ///         scan: &TableScan,
169    ///         _session: &dyn Session,
170    ///         _planning_ctx: &PhysicalPlanningContext,
171    ///     ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
172    ///         // Check if this is your custom table source
173    ///         if scan.source.is::<MyCustomTableSource>() {
174    ///             // Create a custom execution plan for your table source
175    ///             let exec = MyCustomExec::new(
176    ///                 scan.table_name.clone(),
177    ///                 Arc::clone(scan.projected_schema.inner()),
178    ///             );
179    ///             Ok(Some(Arc::new(exec)))
180    ///         } else {
181    ///             // Return None to let other extension planners handle it
182    ///             Ok(None)
183    ///         }
184    ///     }
185    /// }
186    /// ```
187    ///
188    /// [`TableSource`]: datafusion_expr::TableSource
189    async fn plan_table_scan(
190        &self,
191        _planner: &dyn PhysicalPlanner,
192        _scan: &TableScan,
193        _session: &dyn Session,
194        _planning_ctx: &PhysicalPlanningContext,
195    ) -> Result<Option<Arc<dyn ExecutionPlan>>> {
196        Ok(None)
197    }
198}