Skip to main content

datafusion_expr/
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//! [`ContextProvider`] and [`ExprPlanner`] APIs to customize SQL query planning
19
20use std::fmt::Debug;
21use std::sync::Arc;
22
23use crate::expr::NullTreatment;
24#[cfg(feature = "sql")]
25use crate::logical_plan::LogicalPlan;
26use crate::{
27    AggregateUDF, Expr, GetFieldAccess, HigherOrderUDF, ScalarUDF, SortExpr, TableSource,
28    WindowFrame, WindowFunctionDefinition, WindowUDF,
29};
30use arrow::datatypes::{DataType, Field, FieldRef, SchemaRef};
31use datafusion_common::datatype::DataTypeExt;
32use datafusion_common::{
33    DFSchema, Result, TableReference, config::ConfigOptions,
34    file_options::file_type::FileType, not_impl_err,
35};
36#[cfg(feature = "sql")]
37use sqlparser::ast::{Expr as SQLExpr, Ident, ObjectName, TableAlias, TableFactor};
38
39/// Provides the `SQL` query planner meta-data about tables and
40/// functions referenced in SQL statements, without a direct dependency on the
41/// `datafusion` Catalog structures such as [`TableProvider`]
42///
43/// [`TableProvider`]: https://docs.rs/datafusion/latest/datafusion/catalog/trait.TableProvider.html
44pub trait ContextProvider {
45    /// Returns a table by reference, if it exists
46    fn get_table_source(&self, name: TableReference) -> Result<Arc<dyn TableSource>>;
47
48    /// Return the type of a file based on its extension (e.g. `.parquet`)
49    ///
50    /// This is used to plan `COPY` statements
51    fn get_file_type(&self, _ext: &str) -> Result<Arc<dyn FileType>> {
52        not_impl_err!("Registered file types are not supported")
53    }
54
55    /// Getter for a table function
56    fn get_table_function_source(
57        &self,
58        _name: &str,
59        _args: Vec<Expr>,
60    ) -> Result<Arc<dyn TableSource>> {
61        not_impl_err!("Table Functions are not supported")
62    }
63
64    /// Provides an intermediate table that is used to expose a recursive CTE
65    /// self-reference during planning and execution.
66    ///
67    /// CTE stands for "Common Table Expression"
68    ///
69    /// # Notes
70    /// We don't directly implement this in [`SqlToRel`] as implementing this function
71    /// often requires access to a table that contains
72    /// execution-related types that can't be a direct dependency
73    /// of the sql crate (for example [`CteWorkTable`]).
74    ///
75    /// The [`ContextProvider`] provides a way to "hide" this dependency.
76    /// The schema argument is the schema to expose for scans of the recursive
77    /// self-reference, which may be more conservative than the final recursive
78    /// query output schema.
79    ///
80    /// [`SqlToRel`]: https://docs.rs/datafusion/latest/datafusion/sql/planner/struct.SqlToRel.html
81    /// [`CteWorkTable`]: https://docs.rs/datafusion/latest/datafusion/datasource/cte_worktable/struct.CteWorkTable.html
82    fn create_cte_work_table(
83        &self,
84        _name: &str,
85        _schema: SchemaRef,
86    ) -> Result<Arc<dyn TableSource>> {
87        not_impl_err!("Recursive CTE is not implemented")
88    }
89
90    /// Return [`ExprPlanner`] extensions for planning expressions
91    fn get_expr_planners(&self) -> &[Arc<dyn ExprPlanner>] {
92        &[]
93    }
94
95    /// Return [`RelationPlanner`] extensions for planning table factors
96    #[cfg(feature = "sql")]
97    fn get_relation_planners(&self) -> &[Arc<dyn RelationPlanner>] {
98        &[]
99    }
100
101    /// Return [`TypePlanner`] extensions for planning data types
102    #[cfg(feature = "sql")]
103    fn get_type_planner(&self) -> Option<Arc<dyn TypePlanner>> {
104        None
105    }
106
107    /// Return the scalar function with a given name, if any
108    fn get_function_meta(&self, name: &str) -> Option<Arc<ScalarUDF>>;
109
110    /// Return the higher order function with a given name, if any
111    fn get_higher_order_meta(&self, name: &str) -> Option<Arc<HigherOrderUDF>>;
112
113    /// Return the aggregate function with a given name, if any
114    fn get_aggregate_meta(&self, name: &str) -> Option<Arc<AggregateUDF>>;
115
116    /// Return the window function with a given name, if any
117    fn get_window_meta(&self, name: &str) -> Option<Arc<WindowUDF>>;
118
119    /// Return the system/user-defined variable type, if any
120    ///
121    /// A user defined variable is typically accessed via `@var_name`
122    fn get_variable_type(&self, variable_names: &[String]) -> Option<DataType>;
123
124    /// Return metadata about a system/user-defined variable, if any.
125    ///
126    /// By default, this wraps [`Self::get_variable_type`] in an Arrow [`Field`]
127    /// with nullable set to `true` and no metadata. Implementations that can
128    /// provide richer information (such as nullability or extension metadata)
129    /// should override this method.
130    fn get_variable_field(&self, variable_names: &[String]) -> Option<FieldRef> {
131        self.get_variable_type(variable_names)
132            .map(|data_type| data_type.into_nullable_field_ref())
133    }
134
135    /// Return overall configuration options
136    fn options(&self) -> &ConfigOptions;
137
138    /// Return all scalar function names
139    fn udf_names(&self) -> Vec<String>;
140
141    /// Return all higher order function names
142    fn higher_order_function_names(&self) -> Vec<String>;
143
144    /// Return all aggregate function names
145    fn udaf_names(&self) -> Vec<String>;
146
147    /// Return all window function names
148    fn udwf_names(&self) -> Vec<String>;
149}
150
151/// Customize planning of SQL AST expressions to [`Expr`]s
152///
153/// For more background, please also see the [Extending SQL in DataFusion: from ->> to TABLESAMPLE blog]
154///
155/// [Extending SQL in DataFusion: from ->> to TABLESAMPLE blog]: https://datafusion.apache.org/blog/2026/01/12/extending-sql
156pub trait ExprPlanner: Debug + Send + Sync {
157    /// Plan the binary operation between two expressions, returns original
158    /// BinaryExpr if not possible
159    fn plan_binary_op(
160        &self,
161        expr: RawBinaryExpr,
162        _schema: &DFSchema,
163    ) -> Result<PlannerResult<RawBinaryExpr>> {
164        Ok(PlannerResult::Original(expr))
165    }
166
167    /// Plan the field access expression, such as `foo.bar`
168    ///
169    /// returns original [`RawFieldAccessExpr`] if not possible
170    fn plan_field_access(
171        &self,
172        expr: RawFieldAccessExpr,
173        _schema: &DFSchema,
174    ) -> Result<PlannerResult<RawFieldAccessExpr>> {
175        Ok(PlannerResult::Original(expr))
176    }
177
178    /// Plan an array literal, such as `[1, 2, 3]`
179    ///
180    /// Returns original expression arguments if not possible
181    fn plan_array_literal(
182        &self,
183        exprs: Vec<Expr>,
184        _schema: &DFSchema,
185    ) -> Result<PlannerResult<Vec<Expr>>> {
186        Ok(PlannerResult::Original(exprs))
187    }
188
189    /// Plan a `POSITION` expression, such as `POSITION(<expr> in <expr>)`
190    ///
191    /// Returns original expression arguments if not possible
192    fn plan_position(&self, args: Vec<Expr>) -> Result<PlannerResult<Vec<Expr>>> {
193        Ok(PlannerResult::Original(args))
194    }
195
196    /// Plan a dictionary literal, such as `{ key: value, ...}`
197    ///
198    /// Returns original expression arguments if not possible
199    fn plan_dictionary_literal(
200        &self,
201        expr: RawDictionaryExpr,
202        _schema: &DFSchema,
203    ) -> Result<PlannerResult<RawDictionaryExpr>> {
204        Ok(PlannerResult::Original(expr))
205    }
206
207    /// Plan an extract expression, such as`EXTRACT(month FROM foo)`
208    ///
209    /// Returns original expression arguments if not possible
210    fn plan_extract(&self, args: Vec<Expr>) -> Result<PlannerResult<Vec<Expr>>> {
211        Ok(PlannerResult::Original(args))
212    }
213
214    /// Plan an substring expression, such as `SUBSTRING(<expr> [FROM <expr>] [FOR <expr>])`
215    ///
216    /// Returns original expression arguments if not possible
217    fn plan_substring(&self, args: Vec<Expr>) -> Result<PlannerResult<Vec<Expr>>> {
218        Ok(PlannerResult::Original(args))
219    }
220
221    /// Plans a struct literal, such as  `{'field1' : expr1, 'field2' : expr2, ...}`
222    ///
223    /// This function takes a vector of expressions and a boolean flag
224    /// indicating whether the struct uses the optional name
225    ///
226    /// Returns the original input expressions if planning is not possible.
227    fn plan_struct_literal(
228        &self,
229        args: Vec<Expr>,
230        _is_named_struct: bool,
231    ) -> Result<PlannerResult<Vec<Expr>>> {
232        Ok(PlannerResult::Original(args))
233    }
234
235    /// Plans an overlay expression, such as `overlay(str PLACING substr FROM pos [FOR count])`
236    ///
237    /// Returns original expression arguments if not possible
238    fn plan_overlay(&self, args: Vec<Expr>) -> Result<PlannerResult<Vec<Expr>>> {
239        Ok(PlannerResult::Original(args))
240    }
241
242    /// Plans a `make_map` expression, such as `make_map(key1, value1, key2, value2, ...)`
243    ///
244    /// Returns original expression arguments if not possible
245    fn plan_make_map(&self, args: Vec<Expr>) -> Result<PlannerResult<Vec<Expr>>> {
246        Ok(PlannerResult::Original(args))
247    }
248
249    /// Plans compound identifier such as `db.schema.table` for non-empty nested names
250    ///
251    /// # Note:
252    /// Currently compound identifier for outer query schema is not supported.
253    ///
254    /// Returns original expression if not possible
255    fn plan_compound_identifier(
256        &self,
257        _field: &Field,
258        _qualifier: Option<&TableReference>,
259        _nested_names: &[String],
260    ) -> Result<PlannerResult<Vec<Expr>>> {
261        not_impl_err!(
262            "Default planner compound identifier hasn't been implemented for ExprPlanner"
263        )
264    }
265
266    /// Plans aggregate functions, such as `COUNT(<expr>)`
267    ///
268    /// Returns original expression arguments if not possible
269    fn plan_aggregate(
270        &self,
271        expr: RawAggregateExpr,
272    ) -> Result<PlannerResult<RawAggregateExpr>> {
273        Ok(PlannerResult::Original(expr))
274    }
275
276    /// Plans window functions, such as `COUNT(<expr>)`
277    ///
278    /// Returns original expression arguments if not possible
279    fn plan_window(&self, expr: RawWindowExpr) -> Result<PlannerResult<RawWindowExpr>> {
280        Ok(PlannerResult::Original(expr))
281    }
282}
283
284/// An operator with two arguments to plan
285///
286/// Note `left` and `right` are DataFusion [`Expr`]s but the `op` is the SQL AST
287/// operator.
288///
289/// This structure is used by [`ExprPlanner`] to plan operators with
290/// custom expressions.
291#[derive(Debug, Clone)]
292pub struct RawBinaryExpr {
293    #[cfg(not(feature = "sql"))]
294    pub op: datafusion_expr_common::operator::Operator,
295    #[cfg(feature = "sql")]
296    pub op: sqlparser::ast::BinaryOperator,
297    pub left: Expr,
298    pub right: Expr,
299}
300
301/// An expression with GetFieldAccess to plan
302///
303/// This structure is used by [`ExprPlanner`] to plan operators with
304/// custom expressions.
305#[derive(Debug, Clone)]
306pub struct RawFieldAccessExpr {
307    pub field_access: GetFieldAccess,
308    pub expr: Expr,
309}
310
311/// A Dictionary literal expression `{ key: value, ...}`
312///
313/// This structure is used by [`ExprPlanner`] to plan operators with
314/// custom expressions.
315#[derive(Debug, Clone)]
316pub struct RawDictionaryExpr {
317    pub keys: Vec<Expr>,
318    pub values: Vec<Expr>,
319}
320
321/// This structure is used by `AggregateFunctionPlanner` to plan operators with
322/// custom expressions.
323#[derive(Debug, Clone)]
324pub struct RawAggregateExpr {
325    pub func: Arc<AggregateUDF>,
326    pub args: Vec<Expr>,
327    pub distinct: bool,
328    pub filter: Option<Box<Expr>>,
329    pub order_by: Vec<SortExpr>,
330    pub null_treatment: Option<NullTreatment>,
331}
332
333/// This structure is used by `WindowFunctionPlanner` to plan operators with
334/// custom expressions.
335#[derive(Debug, Clone)]
336pub struct RawWindowExpr {
337    pub func_def: WindowFunctionDefinition,
338    pub args: Vec<Expr>,
339    pub partition_by: Vec<Expr>,
340    pub order_by: Vec<SortExpr>,
341    pub window_frame: WindowFrame,
342    pub filter: Option<Box<Expr>>,
343    pub null_treatment: Option<NullTreatment>,
344    pub distinct: bool,
345}
346
347/// Result of planning a raw expr with [`ExprPlanner`]
348#[derive(Debug, Clone)]
349pub enum PlannerResult<T> {
350    /// The raw expression was successfully planned as a new [`Expr`]
351    Planned(Expr),
352    /// The raw expression could not be planned, and is returned unmodified
353    Original(T),
354}
355
356/// Result of planning a relation with [`RelationPlanner`]
357#[cfg(feature = "sql")]
358#[derive(Debug, Clone)]
359pub struct PlannedRelation {
360    /// The logical plan for the relation
361    pub plan: LogicalPlan,
362    /// Optional table alias for the relation
363    pub alias: Option<TableAlias>,
364}
365
366#[cfg(feature = "sql")]
367impl PlannedRelation {
368    /// Create a new `PlannedRelation` with the given plan and alias
369    pub fn new(plan: LogicalPlan, alias: Option<TableAlias>) -> Self {
370        Self { plan, alias }
371    }
372}
373
374/// Result of attempting to plan a relation with extension planners
375#[cfg(feature = "sql")]
376#[derive(Debug)]
377pub enum RelationPlanning {
378    /// The relation was successfully planned by an extension planner
379    Planned(Box<PlannedRelation>),
380    /// No extension planner handled the relation, return it for default processing
381    Original(Box<TableFactor>),
382}
383
384/// Customize planning SQL table factors to [`LogicalPlan`]s.
385#[cfg(feature = "sql")]
386/// For more background, please also see the [Extending SQL in DataFusion: from ->> to TABLESAMPLE blog]
387///
388/// [Extending SQL in DataFusion: from ->> to TABLESAMPLE blog]: https://datafusion.apache.org/blog/2026/01/12/extending-sql
389pub trait RelationPlanner: Debug + Send + Sync {
390    /// Plan a table factor into a [`LogicalPlan`].
391    ///
392    /// Returning [`RelationPlanning::Planned`] short-circuits further planning and uses the
393    /// provided plan. Returning [`RelationPlanning::Original`] allows the next registered planner,
394    /// or DataFusion's default logic, to handle the relation.
395    fn plan_relation(
396        &self,
397        relation: TableFactor,
398        context: &mut dyn RelationPlannerContext,
399    ) -> Result<RelationPlanning>;
400}
401
402/// Provides utilities for relation planners to interact with DataFusion's SQL
403/// planner.
404///
405/// This trait provides SQL planning utilities specific to relation planning,
406/// such as converting SQL expressions to logical expressions and normalizing
407/// identifiers. It uses composition to provide access to session context via
408/// [`ContextProvider`].
409#[cfg(feature = "sql")]
410pub trait RelationPlannerContext {
411    /// Provides access to the underlying context provider for reading session
412    /// configuration, accessing tables, functions, and other metadata.
413    fn context_provider(&self) -> &dyn ContextProvider;
414
415    /// Plans the specified relation through the full planner pipeline, starting
416    /// from the first registered relation planner.
417    fn plan(&mut self, relation: TableFactor) -> Result<LogicalPlan>;
418
419    /// Converts a SQL expression into a logical expression using the current
420    /// planner context.
421    fn sql_to_expr(&mut self, expr: SQLExpr, schema: &DFSchema) -> Result<Expr>;
422
423    /// Converts a SQL expression into a logical expression without DataFusion
424    /// rewrites.
425    fn sql_expr_to_logical_expr(
426        &mut self,
427        expr: SQLExpr,
428        schema: &DFSchema,
429    ) -> Result<Expr>;
430
431    /// Normalizes an identifier according to session settings.
432    fn normalize_ident(&self, ident: Ident) -> String;
433
434    /// Normalizes a SQL object name into a [`TableReference`].
435    fn object_name_to_table_reference(&self, name: ObjectName) -> Result<TableReference>;
436}
437
438/// Customize planning SQL types to DataFusion (Arrow) types.
439#[cfg(feature = "sql")]
440/// For more background, please also see the [Extending SQL in DataFusion: from ->> to TABLESAMPLE blog]
441///
442/// [Extending SQL in DataFusion: from ->> to TABLESAMPLE blog]: https://datafusion.apache.org/blog/2026/01/12/extending-sql
443pub trait TypePlanner: Debug + Send + Sync {
444    /// Plan SQL [`sqlparser::ast::DataType`] to DataFusion [`DataType`]
445    ///
446    /// Returns None if not possible
447    #[deprecated(since = "53.0.0", note = "Use plan_type_field()")]
448    fn plan_type(
449        &self,
450        _sql_type: &sqlparser::ast::DataType,
451    ) -> Result<Option<DataType>> {
452        Ok(None)
453    }
454
455    /// Plan SQL [`sqlparser::ast::DataType`] to DataFusion [`FieldRef`]
456    ///
457    /// Returns None if not possible. Unlike [`Self::plan_type`], `plan_type_field()`
458    /// makes it possible to express extension types (e.g., `arrow.uuid`) or otherwise
459    /// insert metadata into the DataFusion type representation. The default implementation
460    /// falls back on [`Self::plan_type`] for backward compatibility and wraps the result
461    /// in a nullable field reference.
462    fn plan_type_field(
463        &self,
464        sql_type: &sqlparser::ast::DataType,
465    ) -> Result<Option<FieldRef>> {
466        #[expect(deprecated)]
467        Ok(self
468            .plan_type(sql_type)?
469            .map(|data_type| data_type.into_nullable_field_ref()))
470    }
471}