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}