Skip to content

fix: try to lower plain reserved functions to columns as well #16669

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions datafusion/sql/src/expr/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ struct FunctionArgs {
distinct: bool,
/// WITHIN GROUP clause, if any
within_group: Vec<OrderByExpr>,
/// Was the function called without parenthesis, i.e. could this also be a column reference?
function_without_paranthesis: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a typo here:

Suggested change
function_without_paranthesis: bool,
function_without_parenthesis: bool,

}

impl FunctionArgs {
Expand All @@ -118,6 +120,7 @@ impl FunctionArgs {
null_treatment,
distinct: false,
within_group,
function_without_paranthesis: matches!(args, FunctionArguments::None),
});
};

Expand Down Expand Up @@ -199,6 +202,7 @@ impl FunctionArgs {
null_treatment,
distinct,
within_group,
function_without_paranthesis: false,
})
}
}
Expand All @@ -212,14 +216,15 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
) -> Result<Expr> {
let function_args = FunctionArgs::try_new(function)?;
let FunctionArgs {
name,
name: object_name,
args,
order_by,
over,
filter,
null_treatment,
distinct,
within_group,
function_without_paranthesis,
} = function_args;

if over.is_some() && !within_group.is_empty() {
Expand All @@ -235,18 +240,18 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
// it shouldn't have ordering requirement as function argument
// required ordering should be defined in OVER clause.
let is_function_window = over.is_some();
let sql_parser_span = name.0[0].span();
let name = if name.0.len() > 1 {
let sql_parser_span = object_name.0[0].span();
let name = if object_name.0.len() > 1 {
// DF doesn't handle compound identifiers
// (e.g. "foo.bar") for function names yet
name.to_string()
object_name.to_string()
} else {
match name.0[0].as_ident() {
match object_name.0[0].as_ident() {
Some(ident) => crate::utils::normalize_ident(ident.clone()),
None => {
return plan_err!(
"Expected an identifier in function name, but found {:?}",
name.0[0]
object_name.0[0]
)
}
}
Expand Down Expand Up @@ -462,6 +467,31 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
)));
}
}

// workaround for https://github.com/apache/datafusion-sqlparser-rs/issues/1909
if function_without_paranthesis {
let maybe_ids = object_name
.0
.iter()
.map(|part| part.as_ident().cloned().ok_or(()))
.collect::<Result<Vec<_>, ()>>();
if let Ok(ids) = maybe_ids {
if ids.len() == 1 {
return self.sql_identifier_to_expr(
ids.into_iter().next().unwrap(),
schema,
planner_context,
);
} else {
return self.sql_compound_identifier_to_expr(
ids,
schema,
planner_context,
);
}
}
}

// Could not find the relevant function, so return an error
if let Some(suggested_func_name) =
suggest_valid_function(&name, is_function_window, self.context_provider)
Expand Down
21 changes: 3 additions & 18 deletions datafusion/sql/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ use datafusion_expr::planner::{
};
use sqlparser::ast::{
AccessExpr, BinaryOperator, CastFormat, CastKind, DataType as SQLDataType,
DictionaryField, Expr as SQLExpr, ExprWithAlias as SQLExprWithAlias,
FunctionArguments, MapEntry, StructField, Subscript, TrimWhereField, Value,
ValueWithSpan,
DictionaryField, Expr as SQLExpr, ExprWithAlias as SQLExprWithAlias, MapEntry,
StructField, Subscript, TrimWhereField, Value, ValueWithSpan,
};

use datafusion_common::{
Expand Down Expand Up @@ -477,21 +476,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
),

SQLExpr::Function(function) => {
// workaround for https://github.com/apache/datafusion-sqlparser-rs/issues/1909
if matches!(function.args, FunctionArguments::None)
&& function.name.0.len() > 1
&& function.name.0.iter().all(|part| part.as_ident().is_some())
{
let ids = function
.name
.0
.iter()
.map(|part| part.as_ident().expect("just checked").clone())
.collect();
self.sql_compound_identifier_to_expr(ids, schema, planner_context)
} else {
self.sql_function_to_expr(function, schema, planner_context)
}
self.sql_function_to_expr(function, schema, planner_context)
}

SQLExpr::Rollup(exprs) => {
Expand Down
49 changes: 48 additions & 1 deletion datafusion/sqllogictest/test_files/select.slt
Original file line number Diff line number Diff line change
Expand Up @@ -1875,10 +1875,57 @@ drop table t;
# test "user" column
# See https://github.com/apache/datafusion/issues/14141
statement count 0
create table t_with_user(a int, user text) as values (1,'test'), (2,null);
create table t_with_user(a int, user text) as values (1,'test'), (2,null), (3,'foo');

query T
select t_with_user.user from t_with_user;
----
test
NULL
foo

query IT
select * from t_with_user where t_with_user.user = 'foo';
----
3 foo

query T
select user from t_with_user;
----
test
NULL
foo

query IT
select * from t_with_user where user = 'foo';
----
3 foo

# test "current_time" column
# See https://github.com/apache/datafusion/issues/14141
statement count 0
create table t_with_current_time(a int, current_time text) as values (1,'now'), (2,null), (3,'later');

# here it's clear the the column was meant
query B
select t_with_current_time.current_time is not null from t_with_current_time;
----
true
false
true

# here it's the function
query B
select current_time is not null from t_with_current_time;
----
true
true
true

# and here it's the column again
query B
select "current_time" is not null from t_with_current_time;
----
true
false
true