Skip to content

Commit 2884612

Browse files
authored
fix: try to lower plain reserved functions to columns as well (#16669)
When a reserved function like `user` is called without parenthesis, it may as well be a column. This works in PostgreSQL for example: ```text psql (17.5 (Debian 17.5-1.pgdg120+1)) Type "help" for help. postgres=# create table t(a int, "user" text); CREATE TABLE postgres=# insert into t values (1, 'foo'); INSERT 0 1 postgres=# select t.user from t; user ------ foo (1 row) postgres=# select user from t; user ---------- postgres (1 row) ``` However sqlparser tries to detect these functions, see apache/datafusion-sqlparser-rs#1909 We now first try to use the respective function and then also consider columns. Fixes #14141.
1 parent 985eb49 commit 2884612

File tree

3 files changed

+87
-25
lines changed

3 files changed

+87
-25
lines changed

datafusion/sql/src/expr/function.rs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ struct FunctionArgs {
9393
distinct: bool,
9494
/// WITHIN GROUP clause, if any
9595
within_group: Vec<OrderByExpr>,
96+
/// Was the function called without parenthesis, i.e. could this also be a column reference?
97+
function_without_paranthesis: bool,
9698
}
9799

98100
impl FunctionArgs {
@@ -118,6 +120,7 @@ impl FunctionArgs {
118120
null_treatment,
119121
distinct: false,
120122
within_group,
123+
function_without_paranthesis: matches!(args, FunctionArguments::None),
121124
});
122125
};
123126

@@ -199,6 +202,7 @@ impl FunctionArgs {
199202
null_treatment,
200203
distinct,
201204
within_group,
205+
function_without_paranthesis: false,
202206
})
203207
}
204208
}
@@ -212,14 +216,15 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
212216
) -> Result<Expr> {
213217
let function_args = FunctionArgs::try_new(function)?;
214218
let FunctionArgs {
215-
name,
219+
name: object_name,
216220
args,
217221
order_by,
218222
over,
219223
filter,
220224
null_treatment,
221225
distinct,
222226
within_group,
227+
function_without_paranthesis,
223228
} = function_args;
224229

225230
if over.is_some() && !within_group.is_empty() {
@@ -235,18 +240,18 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
235240
// it shouldn't have ordering requirement as function argument
236241
// required ordering should be defined in OVER clause.
237242
let is_function_window = over.is_some();
238-
let sql_parser_span = name.0[0].span();
239-
let name = if name.0.len() > 1 {
243+
let sql_parser_span = object_name.0[0].span();
244+
let name = if object_name.0.len() > 1 {
240245
// DF doesn't handle compound identifiers
241246
// (e.g. "foo.bar") for function names yet
242-
name.to_string()
247+
object_name.to_string()
243248
} else {
244-
match name.0[0].as_ident() {
249+
match object_name.0[0].as_ident() {
245250
Some(ident) => crate::utils::normalize_ident(ident.clone()),
246251
None => {
247252
return plan_err!(
248253
"Expected an identifier in function name, but found {:?}",
249-
name.0[0]
254+
object_name.0[0]
250255
)
251256
}
252257
}
@@ -461,6 +466,31 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
461466
)));
462467
}
463468
}
469+
470+
// workaround for https://github.com/apache/datafusion-sqlparser-rs/issues/1909
471+
if function_without_paranthesis {
472+
let maybe_ids = object_name
473+
.0
474+
.iter()
475+
.map(|part| part.as_ident().cloned().ok_or(()))
476+
.collect::<Result<Vec<_>, ()>>();
477+
if let Ok(ids) = maybe_ids {
478+
if ids.len() == 1 {
479+
return self.sql_identifier_to_expr(
480+
ids.into_iter().next().unwrap(),
481+
schema,
482+
planner_context,
483+
);
484+
} else {
485+
return self.sql_compound_identifier_to_expr(
486+
ids,
487+
schema,
488+
planner_context,
489+
);
490+
}
491+
}
492+
}
493+
464494
// Could not find the relevant function, so return an error
465495
if let Some(suggested_func_name) =
466496
suggest_valid_function(&name, is_function_window, self.context_provider)

datafusion/sql/src/expr/mod.rs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ use datafusion_expr::planner::{
2121
};
2222
use sqlparser::ast::{
2323
AccessExpr, BinaryOperator, CastFormat, CastKind, DataType as SQLDataType,
24-
DictionaryField, Expr as SQLExpr, ExprWithAlias as SQLExprWithAlias,
25-
FunctionArguments, MapEntry, StructField, Subscript, TrimWhereField, Value,
26-
ValueWithSpan,
24+
DictionaryField, Expr as SQLExpr, ExprWithAlias as SQLExprWithAlias, MapEntry,
25+
StructField, Subscript, TrimWhereField, Value, ValueWithSpan,
2726
};
2827

2928
use datafusion_common::{
@@ -477,21 +476,7 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
477476
),
478477

479478
SQLExpr::Function(function) => {
480-
// workaround for https://github.com/apache/datafusion-sqlparser-rs/issues/1909
481-
if matches!(function.args, FunctionArguments::None)
482-
&& function.name.0.len() > 1
483-
&& function.name.0.iter().all(|part| part.as_ident().is_some())
484-
{
485-
let ids = function
486-
.name
487-
.0
488-
.iter()
489-
.map(|part| part.as_ident().expect("just checked").clone())
490-
.collect();
491-
self.sql_compound_identifier_to_expr(ids, schema, planner_context)
492-
} else {
493-
self.sql_function_to_expr(function, schema, planner_context)
494-
}
479+
self.sql_function_to_expr(function, schema, planner_context)
495480
}
496481

497482
SQLExpr::Rollup(exprs) => {

datafusion/sqllogictest/test_files/select.slt

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1875,10 +1875,57 @@ drop table t;
18751875
# test "user" column
18761876
# See https://github.com/apache/datafusion/issues/14141
18771877
statement count 0
1878-
create table t_with_user(a int, user text) as values (1,'test'), (2,null);
1878+
create table t_with_user(a int, user text) as values (1,'test'), (2,null), (3,'foo');
18791879

18801880
query T
18811881
select t_with_user.user from t_with_user;
18821882
----
18831883
test
18841884
NULL
1885+
foo
1886+
1887+
query IT
1888+
select * from t_with_user where t_with_user.user = 'foo';
1889+
----
1890+
3 foo
1891+
1892+
query T
1893+
select user from t_with_user;
1894+
----
1895+
test
1896+
NULL
1897+
foo
1898+
1899+
query IT
1900+
select * from t_with_user where user = 'foo';
1901+
----
1902+
3 foo
1903+
1904+
# test "current_time" column
1905+
# See https://github.com/apache/datafusion/issues/14141
1906+
statement count 0
1907+
create table t_with_current_time(a int, current_time text) as values (1,'now'), (2,null), (3,'later');
1908+
1909+
# here it's clear the the column was meant
1910+
query B
1911+
select t_with_current_time.current_time is not null from t_with_current_time;
1912+
----
1913+
true
1914+
false
1915+
true
1916+
1917+
# here it's the function
1918+
query B
1919+
select current_time is not null from t_with_current_time;
1920+
----
1921+
true
1922+
true
1923+
true
1924+
1925+
# and here it's the column again
1926+
query B
1927+
select "current_time" is not null from t_with_current_time;
1928+
----
1929+
true
1930+
false
1931+
true

0 commit comments

Comments
 (0)