Skip to content

Commit 7788b90

Browse files
alambviirya
andauthored
Minor: error on unsupported RESPECT NULLs syntax (#7998)
* Minor: error on unsupported RESPECT NULLs syntax * fix clippy * Update datafusion/sql/tests/sql_integration.rs Co-authored-by: Liang-Chi Hsieh <viirya@gmail.com> --------- Co-authored-by: Liang-Chi Hsieh <viirya@gmail.com>
1 parent 7d1cf91 commit 7788b90

2 files changed

Lines changed: 43 additions & 35 deletions

File tree

datafusion/sql/src/expr/function.rs

Lines changed: 33 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,44 +36,57 @@ use super::arrow_cast::ARROW_CAST_NAME;
3636
impl<'a, S: ContextProvider> SqlToRel<'a, S> {
3737
pub(super) fn sql_function_to_expr(
3838
&self,
39-
mut function: SQLFunction,
39+
function: SQLFunction,
4040
schema: &DFSchema,
4141
planner_context: &mut PlannerContext,
4242
) -> Result<Expr> {
43-
let name = if function.name.0.len() > 1 {
43+
let SQLFunction {
44+
name,
45+
args,
46+
over,
47+
distinct,
48+
filter,
49+
null_treatment,
50+
special: _, // true if not called with trailing parens
51+
order_by,
52+
} = function;
53+
54+
if let Some(null_treatment) = null_treatment {
55+
return not_impl_err!("Null treatment in aggregate functions is not supported: {null_treatment}");
56+
}
57+
58+
let name = if name.0.len() > 1 {
4459
// DF doesn't handle compound identifiers
4560
// (e.g. "foo.bar") for function names yet
46-
function.name.to_string()
61+
name.to_string()
4762
} else {
48-
crate::utils::normalize_ident(function.name.0[0].clone())
63+
crate::utils::normalize_ident(name.0[0].clone())
4964
};
5065

5166
// user-defined function (UDF) should have precedence in case it has the same name as a scalar built-in function
5267
if let Some(fm) = self.context_provider.get_function_meta(&name) {
53-
let args =
54-
self.function_args_to_expr(function.args, schema, planner_context)?;
68+
let args = self.function_args_to_expr(args, schema, planner_context)?;
5569
return Ok(Expr::ScalarUDF(ScalarUDF::new(fm, args)));
5670
}
5771

5872
// next, scalar built-in
5973
if let Ok(fun) = BuiltinScalarFunction::from_str(&name) {
60-
let args =
61-
self.function_args_to_expr(function.args, schema, planner_context)?;
74+
let args = self.function_args_to_expr(args, schema, planner_context)?;
6275
return Ok(Expr::ScalarFunction(ScalarFunction::new(fun, args)));
6376
};
6477

6578
// If function is a window function (it has an OVER clause),
6679
// it shouldn't have ordering requirement as function argument
6780
// required ordering should be defined in OVER clause.
68-
let is_function_window = function.over.is_some();
69-
if !function.order_by.is_empty() && is_function_window {
81+
let is_function_window = over.is_some();
82+
if !order_by.is_empty() && is_function_window {
7083
return plan_err!(
7184
"Aggregate ORDER BY is not implemented for window functions"
7285
);
7386
}
7487

7588
// then, window function
76-
if let Some(WindowType::WindowSpec(window)) = function.over.take() {
89+
if let Some(WindowType::WindowSpec(window)) = over {
7790
let partition_by = window
7891
.partition_by
7992
.into_iter()
@@ -97,11 +110,8 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
97110
if let Ok(fun) = self.find_window_func(&name) {
98111
let expr = match fun {
99112
WindowFunction::AggregateFunction(aggregate_fun) => {
100-
let args = self.function_args_to_expr(
101-
function.args,
102-
schema,
103-
planner_context,
104-
)?;
113+
let args =
114+
self.function_args_to_expr(args, schema, planner_context)?;
105115

106116
Expr::WindowFunction(expr::WindowFunction::new(
107117
WindowFunction::AggregateFunction(aggregate_fun),
@@ -113,11 +123,7 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
113123
}
114124
_ => Expr::WindowFunction(expr::WindowFunction::new(
115125
fun,
116-
self.function_args_to_expr(
117-
function.args,
118-
schema,
119-
planner_context,
120-
)?,
126+
self.function_args_to_expr(args, schema, planner_context)?,
121127
partition_by,
122128
order_by,
123129
window_frame,
@@ -128,26 +134,19 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
128134
} else {
129135
// User defined aggregate functions (UDAF) have precedence in case it has the same name as a scalar built-in function
130136
if let Some(fm) = self.context_provider.get_aggregate_meta(&name) {
131-
let args =
132-
self.function_args_to_expr(function.args, schema, planner_context)?;
137+
let args = self.function_args_to_expr(args, schema, planner_context)?;
133138
return Ok(Expr::AggregateUDF(expr::AggregateUDF::new(
134139
fm, args, None, None,
135140
)));
136141
}
137142

138143
// next, aggregate built-ins
139144
if let Ok(fun) = AggregateFunction::from_str(&name) {
140-
let distinct = function.distinct;
141-
let order_by = self.order_by_to_sort_expr(
142-
&function.order_by,
143-
schema,
144-
planner_context,
145-
)?;
145+
let order_by =
146+
self.order_by_to_sort_expr(&order_by, schema, planner_context)?;
146147
let order_by = (!order_by.is_empty()).then_some(order_by);
147-
let args =
148-
self.function_args_to_expr(function.args, schema, planner_context)?;
149-
let filter: Option<Box<Expr>> = function
150-
.filter
148+
let args = self.function_args_to_expr(args, schema, planner_context)?;
149+
let filter: Option<Box<Expr>> = filter
151150
.map(|e| self.sql_expr_to_logical_expr(*e, schema, planner_context))
152151
.transpose()?
153152
.map(Box::new);
@@ -159,8 +158,7 @@ impl<'a, S: ContextProvider> SqlToRel<'a, S> {
159158

160159
// Special case arrow_cast (as its type is dependent on its argument value)
161160
if name == ARROW_CAST_NAME {
162-
let args =
163-
self.function_args_to_expr(function.args, schema, planner_context)?;
161+
let args = self.function_args_to_expr(args, schema, planner_context)?;
164162
return super::arrow_cast::create_arrow_cast(args, schema);
165163
}
166164
}

datafusion/sql/tests/sql_integration.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,16 @@ fn select_simple_aggregate_repeated_aggregate_with_unique_aliases() {
12871287
);
12881288
}
12891289

1290+
#[test]
1291+
fn select_simple_aggregate_respect_nulls() {
1292+
let sql = "SELECT MIN(age) RESPECT NULLS FROM person";
1293+
let err = logical_plan(sql).expect_err("query should have failed");
1294+
1295+
assert_contains!(
1296+
err.strip_backtrace(),
1297+
"This feature is not implemented: Null treatment in aggregate functions is not supported: RESPECT NULLS"
1298+
);
1299+
}
12901300
#[test]
12911301
fn select_from_typed_string_values() {
12921302
quick_test(

0 commit comments

Comments
 (0)