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 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
12 changes: 11 additions & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4706,6 +4706,16 @@ 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

///
/// Example:
/// ```plaintext
/// HISTOGRAM(0.5, 0.6)(x, y)
/// ```
///
/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/aggregate-functions/parametric-functions)
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 +4744,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
1 change: 1 addition & 0 deletions src/ast/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ where
/// null_treatment: None,
/// filter: None,
/// over: None,
/// parameters: FunctionArguments::None,
/// within_group: vec![],
/// });
/// }
Expand Down
33 changes: 29 additions & 4 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use core::fmt;

use log::debug;

use recursion::RecursionCounter;
use IsLateral::*;
use IsOptional::*;

Expand Down Expand Up @@ -143,8 +144,6 @@ mod recursion {
pub struct DepthGuard {}
}

use recursion::RecursionCounter;

#[derive(PartialEq, Eq)]
pub enum IsOptional {
Optional,
Expand Down Expand Up @@ -999,6 +998,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 +1055,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 +1291,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 +1300,16 @@ 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 aggregations support parametric functions like `HISTOGRAM(0.5, 0.6)(x, y)`
// which (0.5, 0.6) is a parameter to the function.
if dialect_of!(self is ClickHouseDialect | GenericDialect)
&& self.consume_token(&Token::LParen)
{
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 +1358,7 @@ impl<'a> Parser<'a> {

Ok(Expr::Function(Function {
name,
parameters,
args: FunctionArguments::List(args),
null_treatment,
filter,
Expand Down Expand Up @@ -1379,6 +1391,7 @@ impl<'a> Parser<'a> {
};
Ok(Expr::Function(Function {
name,
parameters: FunctionArguments::None,
args,
filter: None,
over: None,
Expand Down Expand Up @@ -6454,6 +6467,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 Expand Up @@ -8081,7 +8095,7 @@ impl<'a> Parser<'a> {
pub fn parse_query_body(&mut self, precedence: u8) -> Result<SetExpr, ParserError> {
// We parse the expression using a Pratt parser, as in `parse_expr()`.
// Start by parsing a restricted SELECT or a `(subquery)`:
let mut expr = if self.parse_keyword(Keyword::SELECT) {
let expr = if self.parse_keyword(Keyword::SELECT) {
SetExpr::Select(self.parse_select().map(Box::new)?)
} else if self.consume_token(&Token::LParen) {
// CTEs are not allowed here, but the parser currently accepts them
Expand All @@ -8100,6 +8114,17 @@ impl<'a> Parser<'a> {
);
};

self.parse_remaining_set_exprs(expr, precedence)
}

/// Parse any extra set expressions that may be present in a query body
///
Copy link
Contributor

@alamb alamb Jun 23, 2024

Choose a reason for hiding this comment

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

I also pushed a commit to split up this function in e2ec2a6 to get the deeply nested tests to pass

This is the failure without this change: https://github.com/sqlparser-rs/sqlparser-rs/actions/runs/9632995493/job/26566863197


thread 'parse_deeply_nested_parens_hits_recursion_limits' has overflowed its stack
fatal runtime error: stack overflow
error: test failed, to rerun pass `--test sqlparser_common`

/// (this is its own function to reduce required stack size in debug builds)
fn parse_remaining_set_exprs(
&mut self,
mut expr: SetExpr,
precedence: u8,
) -> Result<SetExpr, ParserError> {
loop {
// The query can be optionally followed by a set operator:
let op = self.parse_set_operator(&self.peek_token().token);
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
50 changes: 50 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,55 @@ fn parse_select_star_except() {
clickhouse().verified_stmt("SELECT * EXCEPT (prev_status) FROM anomalies");
}

#[test]
fn parse_select_parametric_function() {
match clickhouse_and_generic().verified_stmt("SELECT HISTOGRAM(0.5, 0.6)(x, y) FROM t") {
Statement::Query(query) => {
let projection: &Vec<SelectItem> = query.body.as_select().unwrap().projection.as_ref();
assert_eq!(projection.len(), 1);
match &projection[0] {
UnnamedExpr(Expr::Function(f)) => {
let args = match &f.args {
FunctionArguments::List(ref args) => args,
_ => unreachable!(),
};
assert_eq!(args.args.len(), 2);
assert_eq!(
args.args[0],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Identifier(Ident::from("x"))))
);
assert_eq!(
args.args[1],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Identifier(Ident::from("y"))))
);

let parameters = match f.parameters {
FunctionArguments::List(ref args) => args,
_ => unreachable!(),
};
assert_eq!(parameters.args.len(), 2);
assert_eq!(
parameters.args[0],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(Value::Number(
"0.5".parse().unwrap(),
false
))))
);
assert_eq!(
parameters.args[1],
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(Value::Number(
"0.6".parse().unwrap(),
false
))))
);
}
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}

#[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
Loading
Loading