Skip to content

Support parametric arguments to FUNCTION for ClickHouse dialect #1315

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 5 commits into from
Jun 23, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 4 additions & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4706,6 +4706,9 @@ impl fmt::Display for CloseCursor {
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Function {
pub name: ObjectName,
/// The parameters to the function, including any options specified within the
/// delimiting parentheses.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we include a link to the clickhouse docs?

Copy link
Member Author

Choose a reason for hiding this comment

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

@iffyio Done

pub parameters: FunctionArguments,
/// The arguments to the function, including any options specified within the
/// delimiting parentheses.
pub args: FunctionArguments,
Expand Down Expand Up @@ -4734,7 +4737,7 @@ pub struct Function {

impl fmt::Display for Function {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.name, self.args)?;
write!(f, "{}{}{}", self.name, self.parameters, self.args)?;

if !self.within_group.is_empty() {
write!(
Expand Down
15 changes: 14 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,7 @@ impl<'a> Parser<'a> {
{
Ok(Expr::Function(Function {
name: ObjectName(vec![w.to_ident()]),
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
filter: None,
Expand Down Expand Up @@ -1055,6 +1056,7 @@ impl<'a> Parser<'a> {
self.expect_token(&Token::RParen)?;
Ok(Expr::Function(Function {
name: ObjectName(vec![w.to_ident()]),
parameters: FunctionArguments::None,
args: FunctionArguments::Subquery(query),
filter: None,
null_treatment: None,
Expand Down Expand Up @@ -1290,6 +1292,7 @@ impl<'a> Parser<'a> {
self.expect_token(&Token::RParen)?;
return Ok(Expr::Function(Function {
name,
parameters: FunctionArguments::None,
args: FunctionArguments::Subquery(subquery),
filter: None,
null_treatment: None,
Expand All @@ -1298,7 +1301,14 @@ impl<'a> Parser<'a> {
}));
}

let args = self.parse_function_argument_list()?;
let mut args = self.parse_function_argument_list()?;
let mut parameters = FunctionArguments::None;
// ClickHouse aggregation support parametric functions like `quantile(0.5)(x)`
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// ClickHouse aggregation support parametric functions like `quantile(0.5)(x)`
// ClickHouse aggregations support parametric functions like `quantile(0.5)(x)`

// which (0.5) is a parameter to the function.
if dialect_of!(self is ClickHouseDialect) && self.consume_token(&Token::LParen) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if dialect_of!(self is ClickHouseDialect) && self.consume_token(&Token::LParen) {
if dialect_of!(self is ClickHouseDialect | GenericDialect) && self.consume_token(&Token::LParen) {

we can include generic dialect if no conflicts

parameters = FunctionArguments::List(args);
args = self.parse_function_argument_list()?;
}

let within_group = if self.parse_keywords(&[Keyword::WITHIN, Keyword::GROUP]) {
self.expect_token(&Token::LParen)?;
Expand Down Expand Up @@ -1347,6 +1357,7 @@ impl<'a> Parser<'a> {

Ok(Expr::Function(Function {
name,
parameters,
args: FunctionArguments::List(args),
null_treatment,
filter,
Expand Down Expand Up @@ -1379,6 +1390,7 @@ impl<'a> Parser<'a> {
};
Ok(Expr::Function(Function {
name,
parameters: FunctionArguments::None,
args,
filter: None,
over: None,
Expand Down Expand Up @@ -6454,6 +6466,7 @@ impl<'a> Parser<'a> {
} else {
Ok(Statement::Call(Function {
name: object_name,
parameters: FunctionArguments::None,
args: FunctionArguments::None,
over: None,
filter: None,
Expand Down
1 change: 1 addition & 0 deletions src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ pub fn join(relation: TableFactor) -> Join {
pub fn call(function: &str, args: impl IntoIterator<Item = Expr>) -> Expr {
Expr::Function(Function {
name: ObjectName(vec![Ident::new(function)]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: args
Expand Down
6 changes: 6 additions & 0 deletions tests/sqlparser_clickhouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ fn parse_delimited_identifiers() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
Expand Down Expand Up @@ -553,6 +554,11 @@ fn parse_select_star_except() {
clickhouse().verified_stmt("SELECT * EXCEPT (prev_status) FROM anomalies");
}

#[test]
fn parse_select_parametric_function() {
clickhouse().verified_stmt("SELECT quantile(0.5)(x) FROM t");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we extend the test asserting that the parameter and arguments lists do indeed show up in the parameters and args fields respectively (i.e not vice versa for example). Also we can include multiple items in both lists e.g quantile(0.5,0.6)(x,y)


#[test]
fn parse_select_star_except_no_parens() {
clickhouse().one_statement_parses_to(
Expand Down
14 changes: 14 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1042,6 +1042,7 @@ fn parse_select_count_wildcard() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("COUNT")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)],
Expand All @@ -1063,6 +1064,7 @@ fn parse_select_count_distinct() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("COUNT")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: Some(DuplicateTreatment::Distinct),
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::UnaryOp {
Expand Down Expand Up @@ -2148,6 +2150,7 @@ fn parse_select_having() {
Some(Expr::BinaryOp {
left: Box::new(Expr::Function(Function {
name: ObjectName(vec![Ident::new("COUNT")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)],
Expand Down Expand Up @@ -2177,6 +2180,7 @@ fn parse_select_qualify() {
Some(Expr::BinaryOp {
left: Box::new(Expr::Function(Function {
name: ObjectName(vec![Ident::new("ROW_NUMBER")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
Expand Down Expand Up @@ -2520,6 +2524,7 @@ fn parse_listagg() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("LISTAGG")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: Some(DuplicateTreatment::Distinct),
args: vec![
Expand Down Expand Up @@ -4224,6 +4229,7 @@ fn parse_named_argument_function() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("FUN")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![
Expand Down Expand Up @@ -4262,6 +4268,7 @@ fn parse_named_argument_function_with_eq_operator() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("FUN")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![
Expand Down Expand Up @@ -4334,6 +4341,7 @@ fn parse_window_functions() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("row_number")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
Expand Down Expand Up @@ -4462,6 +4470,7 @@ fn test_parse_named_window() {
value: "MIN".to_string(),
quote_style: None,
}]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
Expand Down Expand Up @@ -4491,6 +4500,7 @@ fn test_parse_named_window() {
value: "MAX".to_string(),
quote_style: None,
}]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
Expand Down Expand Up @@ -8060,6 +8070,7 @@ fn parse_time_functions() {
let select = verified_only_select(&sql);
let select_localtime_func_call_ast = Function {
name: ObjectName(vec![Ident::new(func_name)]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
Expand Down Expand Up @@ -8988,6 +8999,7 @@ fn parse_call() {
assert_eq!(
verified_stmt("CALL my_procedure('a')"),
Statement::Call(Function {
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
Expand Down Expand Up @@ -9389,6 +9401,7 @@ fn test_selective_aggregation() {
vec![
SelectItem::UnnamedExpr(Expr::Function(Function {
name: ObjectName(vec![Ident::new("ARRAY_AGG")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
Expand All @@ -9406,6 +9419,7 @@ fn test_selective_aggregation() {
SelectItem::ExprWithAlias {
expr: Expr::Function(Function {
name: ObjectName(vec![Ident::new("ARRAY_AGG")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_duckdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,7 @@ fn test_duckdb_named_argument_function_with_assignment_operator() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("FUN")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_hive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ fn parse_delimited_identifiers() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ fn parse_delimited_identifiers() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
Expand Down
7 changes: 7 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2369,6 +2369,7 @@ fn parse_array_subquery_expr() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("ARRAY")]),
parameters: FunctionArguments::None,
args: FunctionArguments::Subquery(Box::new(Query {
with: None,
body: Box::new(SetExpr::SetOperation {
Expand Down Expand Up @@ -2729,6 +2730,7 @@ fn test_composite_value() {
Ident::new("information_schema"),
Ident::new("_pg_expandarray")
]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Array(
Expand Down Expand Up @@ -2955,6 +2957,7 @@ fn parse_current_functions() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("CURRENT_CATALOG")]),
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
filter: None,
Expand All @@ -2966,6 +2969,7 @@ fn parse_current_functions() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("CURRENT_USER")]),
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
filter: None,
Expand All @@ -2977,6 +2981,7 @@ fn parse_current_functions() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("SESSION_USER")]),
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
filter: None,
Expand All @@ -2988,6 +2993,7 @@ fn parse_current_functions() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::new("USER")]),
parameters: FunctionArguments::None,
args: FunctionArguments::None,
null_treatment: None,
filter: None,
Expand Down Expand Up @@ -3438,6 +3444,7 @@ fn parse_delimited_identifiers() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_redshift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ fn parse_delimited_identifiers() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_snowflake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,7 @@ fn parse_delimited_identifiers() {
assert_eq!(
&Expr::Function(Function {
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![],
Expand Down
1 change: 1 addition & 0 deletions tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ fn parse_window_function_with_filter() {
select.projection,
vec![SelectItem::UnnamedExpr(Expr::Function(Function {
name: ObjectName(vec![Ident::new(func_name)]),
parameters: FunctionArguments::None,
args: FunctionArguments::List(FunctionArgumentList {
duplicate_treatment: None,
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
Expand Down