Skip to content

Commit a66ccad

Browse files
samuelcolvinfindepi
authored andcommitted
Allow user defined SQL planners to be registered (apache#11208)
* Allow user defined SQL planners to be registered * fix clippy, remove unused Default * format
1 parent 756e350 commit a66ccad

7 files changed

Lines changed: 134 additions & 8 deletions

File tree

datafusion/core/src/execution/context/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ use datafusion_execution::registry::SerializerRegistry;
6060
use datafusion_expr::{
6161
expr_rewriter::FunctionRewrite,
6262
logical_plan::{DdlStatement, Statement},
63+
planner::UserDefinedSQLPlanner,
6364
Expr, UserDefinedLogicalNode, WindowUDF,
6465
};
6566

@@ -1390,6 +1391,15 @@ impl FunctionRegistry for SessionContext {
13901391
) -> Result<()> {
13911392
self.state.write().register_function_rewrite(rewrite)
13921393
}
1394+
1395+
fn register_user_defined_sql_planner(
1396+
&mut self,
1397+
user_defined_sql_planner: Arc<dyn UserDefinedSQLPlanner>,
1398+
) -> Result<()> {
1399+
self.state
1400+
.write()
1401+
.register_user_defined_sql_planner(user_defined_sql_planner)
1402+
}
13931403
}
13941404

13951405
/// Create a new task context instance from SessionContext

datafusion/core/src/execution/session_state.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ use datafusion_execution::runtime_env::RuntimeEnv;
6060
use datafusion_execution::TaskContext;
6161
use datafusion_expr::execution_props::ExecutionProps;
6262
use datafusion_expr::expr_rewriter::FunctionRewrite;
63+
use datafusion_expr::planner::UserDefinedSQLPlanner;
6364
use datafusion_expr::registry::{FunctionRegistry, SerializerRegistry};
6465
use datafusion_expr::simplify::SimplifyInfo;
6566
use datafusion_expr::var_provider::{is_system_variables, VarType};
@@ -99,6 +100,8 @@ pub struct SessionState {
99100
session_id: String,
100101
/// Responsible for analyzing and rewrite a logical plan before optimization
101102
analyzer: Analyzer,
103+
/// Provides support for customising the SQL planner, e.g. to add support for custom operators like `->>` or `?`
104+
user_defined_sql_planners: Vec<Arc<dyn UserDefinedSQLPlanner>>,
102105
/// Responsible for optimizing a logical plan
103106
optimizer: Optimizer,
104107
/// Responsible for optimizing a physical execution plan
@@ -231,6 +234,7 @@ impl SessionState {
231234
let mut new_self = SessionState {
232235
session_id,
233236
analyzer: Analyzer::new(),
237+
user_defined_sql_planners: vec![],
234238
optimizer: Optimizer::new(),
235239
physical_optimizers: PhysicalOptimizer::new(),
236240
query_planner: Arc::new(DefaultQueryPlanner {}),
@@ -947,16 +951,21 @@ impl SessionState {
947951
where
948952
S: ContextProvider,
949953
{
950-
let query = SqlToRel::new_with_options(provider, self.get_parser_options());
954+
let mut query = SqlToRel::new_with_options(provider, self.get_parser_options());
955+
956+
// custom planners are registered first, so they're run first and take precedence over built-in planners
957+
for planner in self.user_defined_sql_planners.iter() {
958+
query = query.with_user_defined_planner(planner.clone());
959+
}
951960

952961
// register crate of array expressions (if enabled)
953962
#[cfg(feature = "array_expressions")]
954963
{
955964
let array_planner =
956-
Arc::new(functions_array::planner::ArrayFunctionPlanner::default()) as _;
965+
Arc::new(functions_array::planner::ArrayFunctionPlanner) as _;
957966

958967
let field_access_planner =
959-
Arc::new(functions_array::planner::FieldAccessPlanner::default()) as _;
968+
Arc::new(functions_array::planner::FieldAccessPlanner) as _;
960969

961970
query
962971
.with_user_defined_planner(array_planner)
@@ -1176,6 +1185,15 @@ impl FunctionRegistry for SessionState {
11761185
self.analyzer.add_function_rewrite(rewrite);
11771186
Ok(())
11781187
}
1188+
1189+
fn register_user_defined_sql_planner(
1190+
&mut self,
1191+
user_defined_sql_planner: Arc<dyn UserDefinedSQLPlanner>,
1192+
) -> datafusion_common::Result<()> {
1193+
self.user_defined_sql_planners
1194+
.push(user_defined_sql_planner);
1195+
Ok(())
1196+
}
11791197
}
11801198

11811199
impl OptimizerConfig for SessionState {

datafusion/core/tests/user_defined/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,6 @@ mod user_defined_window_functions;
2929

3030
/// Tests for User Defined Table Functions
3131
mod user_defined_table_functions;
32+
33+
/// Tests for User Defined SQL Planner
34+
mod user_defined_sql_planner;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
use arrow_array::RecordBatch;
19+
use std::sync::Arc;
20+
21+
use datafusion::common::{assert_batches_eq, DFSchema};
22+
use datafusion::error::Result;
23+
use datafusion::execution::FunctionRegistry;
24+
use datafusion::logical_expr::Operator;
25+
use datafusion::prelude::*;
26+
use datafusion::sql::sqlparser::ast::BinaryOperator;
27+
use datafusion_expr::planner::{PlannerResult, RawBinaryExpr, UserDefinedSQLPlanner};
28+
use datafusion_expr::BinaryExpr;
29+
30+
struct MyCustomPlanner;
31+
32+
impl UserDefinedSQLPlanner for MyCustomPlanner {
33+
fn plan_binary_op(
34+
&self,
35+
expr: RawBinaryExpr,
36+
_schema: &DFSchema,
37+
) -> Result<PlannerResult<RawBinaryExpr>> {
38+
match &expr.op {
39+
BinaryOperator::Arrow => {
40+
Ok(PlannerResult::Planned(Expr::BinaryExpr(BinaryExpr {
41+
left: Box::new(expr.left.clone()),
42+
right: Box::new(expr.right.clone()),
43+
op: Operator::StringConcat,
44+
})))
45+
}
46+
BinaryOperator::LongArrow => {
47+
Ok(PlannerResult::Planned(Expr::BinaryExpr(BinaryExpr {
48+
left: Box::new(expr.left.clone()),
49+
right: Box::new(expr.right.clone()),
50+
op: Operator::Plus,
51+
})))
52+
}
53+
_ => Ok(PlannerResult::Original(expr)),
54+
}
55+
}
56+
}
57+
58+
async fn plan_and_collect(sql: &str) -> Result<Vec<RecordBatch>> {
59+
let mut ctx = SessionContext::new();
60+
ctx.register_user_defined_sql_planner(Arc::new(MyCustomPlanner))?;
61+
ctx.sql(sql).await?.collect().await
62+
}
63+
64+
#[tokio::test]
65+
async fn test_custom_operators_arrow() {
66+
let actual = plan_and_collect("select 'foo'->'bar';").await.unwrap();
67+
let expected = [
68+
"+----------------------------+",
69+
"| Utf8(\"foo\") || Utf8(\"bar\") |",
70+
"+----------------------------+",
71+
"| foobar |",
72+
"+----------------------------+",
73+
];
74+
assert_batches_eq!(&expected, &actual);
75+
}
76+
77+
#[tokio::test]
78+
async fn test_custom_operators_long_arrow() {
79+
let actual = plan_and_collect("select 1->>2;").await.unwrap();
80+
let expected = [
81+
"+---------------------+",
82+
"| Int64(1) + Int64(2) |",
83+
"+---------------------+",
84+
"| 3 |",
85+
"+---------------------+",
86+
];
87+
assert_batches_eq!(&expected, &actual);
88+
}

datafusion/expr/src/planner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub trait ContextProvider {
8383
}
8484

8585
/// This trait allows users to customize the behavior of the SQL planner
86-
pub trait UserDefinedSQLPlanner {
86+
pub trait UserDefinedSQLPlanner: Send + Sync {
8787
/// Plan the binary operation between two expressions, returns OriginalBinaryExpr if not possible
8888
fn plan_binary_op(
8989
&self,

datafusion/expr/src/registry.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//! FunctionRegistry trait
1919
2020
use crate::expr_rewriter::FunctionRewrite;
21+
use crate::planner::UserDefinedSQLPlanner;
2122
use crate::{AggregateUDF, ScalarUDF, UserDefinedLogicalNode, WindowUDF};
2223
use datafusion_common::{not_impl_err, plan_datafusion_err, Result};
2324
use std::collections::HashMap;
@@ -108,6 +109,14 @@ pub trait FunctionRegistry {
108109
) -> Result<()> {
109110
not_impl_err!("Registering FunctionRewrite")
110111
}
112+
113+
/// Registers a new [`UserDefinedSQLPlanner`] with the registry.
114+
fn register_user_defined_sql_planner(
115+
&mut self,
116+
_user_defined_sql_planner: Arc<dyn UserDefinedSQLPlanner>,
117+
) -> Result<()> {
118+
not_impl_err!("Registering UserDefinedSQLPlanner")
119+
}
111120
}
112121

113122
/// Serializer and deserializer registry for extensions like [UserDefinedLogicalNode].

datafusion/functions-array/src/planner.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ use crate::{
3131
make_array::make_array,
3232
};
3333

34-
#[derive(Default)]
35-
pub struct ArrayFunctionPlanner {}
34+
pub struct ArrayFunctionPlanner;
3635

3736
impl UserDefinedSQLPlanner for ArrayFunctionPlanner {
3837
fn plan_binary_op(
@@ -99,8 +98,7 @@ impl UserDefinedSQLPlanner for ArrayFunctionPlanner {
9998
}
10099
}
101100

102-
#[derive(Default)]
103-
pub struct FieldAccessPlanner {}
101+
pub struct FieldAccessPlanner;
104102

105103
impl UserDefinedSQLPlanner for FieldAccessPlanner {
106104
fn plan_field_access(

0 commit comments

Comments
 (0)