From c101a3066a3d3f33b7c216ffdac9dc5d18cf4aba Mon Sep 17 00:00:00 2001 From: Richard Chien Date: Thu, 22 Aug 2024 16:52:17 +0800 Subject: [PATCH] fix(sqlparser): fix subclause order when parsing function calls (#18130) Signed-off-by: Richard Chien --- .../tests/testdata/output/agg.yaml | 32 ++-- .../tests/testdata/output/expr.yaml | 2 +- .../testdata/output/over_window_function.yaml | 2 +- .../tests/testdata/output/project_set.yaml | 2 +- src/frontend/src/binder/mod.rs | 4 +- src/sqlparser/src/ast/mod.rs | 25 ++- src/sqlparser/src/parser.rs | 45 +++--- src/sqlparser/tests/sqlparser_common.rs | 150 ++++++++++++++---- src/sqlparser/tests/testdata/lambda.yaml | 6 +- .../tests/testdata/qualified_operator.yaml | 4 +- src/sqlparser/tests/testdata/select.yaml | 8 +- 11 files changed, 193 insertions(+), 87 deletions(-) diff --git a/src/frontend/planner_test/tests/testdata/output/agg.yaml b/src/frontend/planner_test/tests/testdata/output/agg.yaml index 33f8266c79747..6a8a00eaa970b 100644 --- a/src/frontend/planner_test/tests/testdata/output/agg.yaml +++ b/src/frontend/planner_test/tests/testdata/output/agg.yaml @@ -769,7 +769,7 @@ create table t(a int, b int); select avg(a) FILTER (WHERE abs(a)) AS avga from t; binder_error: | - Failed to bind expression: avg(a) FILTER(WHERE abs(a)) + Failed to bind expression: avg(a) FILTER (WHERE abs(a)) Caused by: argument of FILTER must be boolean, not type Int32 @@ -778,7 +778,7 @@ create table t(a int, b int); select avg(a) FILTER (WHERE 0 < (select max(a) from t)) AS avga from t; binder_error: | - Failed to bind expression: avg(a) FILTER(WHERE 0 < (SELECT max(a) FROM t)) + Failed to bind expression: avg(a) FILTER (WHERE 0 < (SELECT max(a) FROM t)) Caused by: Feature is not yet implemented: subquery in filter clause @@ -788,7 +788,7 @@ create table t(a int, b int); select avg(a) FILTER (WHERE a < avg(b)) AS avga from t; binder_error: | - Failed to bind expression: avg(a) FILTER(WHERE a < avg(b)) + Failed to bind expression: avg(a) FILTER (WHERE a < avg(b)) Caused by: Feature is not yet implemented: aggregation function in filter clause @@ -798,7 +798,7 @@ create table t(a int, b int); select abs(a) FILTER (WHERE a > 0) AS avga from t; binder_error: | - Failed to bind expression: abs(a) FILTER(WHERE a > 0) + Failed to bind expression: abs(a) FILTER (WHERE a > 0) Caused by: Invalid input syntax: DISTINCT, ORDER BY or FILTER is only allowed in aggregation functions, but `abs` is not an aggregation function @@ -1096,7 +1096,7 @@ create table a (a1 int, a2 int); select count(a1) filter (where (select true)) from a; binder_error: | - Failed to bind expression: count(a1) FILTER(WHERE (SELECT true)) + Failed to bind expression: count(a1) FILTER (WHERE (SELECT true)) Caused by: Feature is not yet implemented: subquery in filter clause @@ -1110,10 +1110,10 @@ select count(b1) filter (where min(a1) < 3) from b ); binder_error: | - Failed to bind expression: EXISTS (SELECT count(b1) FILTER(WHERE min(a1) < 3) FROM b) + Failed to bind expression: EXISTS (SELECT count(b1) FILTER (WHERE min(a1) < 3) FROM b) Caused by these errors (recent errors listed first): - 1: Failed to bind expression: count(b1) FILTER(WHERE min(a1) < 3) + 1: Failed to bind expression: count(b1) FILTER (WHERE min(a1) < 3) 2: Feature is not yet implemented: aggregation function in filter clause No tracking issue yet. Feel free to submit a feature request at https://github.com/risingwavelabs/risingwave/issues/new?labels=type%2Ffeature&template=feature_request.yml - name: agg filter - table function @@ -1122,7 +1122,7 @@ create table a (a1 int, a2 int); select count(a1) filter (where unnest(array[1]) < 1) from a; binder_error: | - Failed to bind expression: count(a1) FILTER(WHERE unnest(ARRAY[1]) < 1) + Failed to bind expression: count(a1) FILTER (WHERE unnest(ARRAY[1]) < 1) Caused by: Invalid input syntax: table functions are not allowed in FILTER @@ -1551,7 +1551,7 @@ create table t (x int, y int); select percentile_cont(x) within group (order by y) from t; binder_error: | - Failed to bind expression: percentile_cont(x) + Failed to bind expression: percentile_cont(x) WITHIN GROUP (ORDER BY y) Caused by: Feature is not yet implemented: variable as direct argument of ordered-set aggregate @@ -1560,7 +1560,7 @@ create table t (x int, y int); select percentile_cont('abc') within group (order by y) from t; binder_error: | - Failed to bind expression: percentile_cont('abc') + Failed to bind expression: percentile_cont('abc') WITHIN GROUP (ORDER BY y) Caused by: Feature is not yet implemented: variable as direct argument of ordered-set aggregate @@ -1569,7 +1569,7 @@ create table t (x int, y int); select percentile_cont(1.3) within group (order by y) from t; binder_error: | - Failed to bind expression: percentile_cont(1.3) + Failed to bind expression: percentile_cont(1.3) WITHIN GROUP (ORDER BY y) Caused by: Invalid input syntax: direct arg in `percentile_cont` must between 0.0 and 1.0 @@ -1577,7 +1577,7 @@ create table t (x int, y int); select percentile_cont(0, 0) within group (order by y) from t; binder_error: | - Failed to bind expression: percentile_cont(0, 0) + Failed to bind expression: percentile_cont(0, 0) WITHIN GROUP (ORDER BY y) Caused by: Invalid input syntax: invalid direct args or within group argument for `percentile_cont` aggregation @@ -1585,7 +1585,7 @@ create table t (x int, y varchar); select percentile_cont(0) within group (order by y) from t; binder_error: | - Failed to bind expression: percentile_cont(0) + Failed to bind expression: percentile_cont(0) WITHIN GROUP (ORDER BY y) Caused by: Invalid input syntax: arg in `percentile_cont` must be castable to float64 @@ -1625,7 +1625,7 @@ create table t (x int, y int); select percentile_cont(0.8 + 0.8) within group (order by y) from t; binder_error: | - Failed to bind expression: percentile_cont(0.8 + 0.8) + Failed to bind expression: percentile_cont(0.8 + 0.8) WITHIN GROUP (ORDER BY y) Caused by: Invalid input syntax: direct arg in `percentile_cont` must between 0.0 and 1.0 @@ -1633,7 +1633,7 @@ create table t (x int, y int); select percentile_cont(0.2 + x) within group (order by y) from t group by x; binder_error: | - Failed to bind expression: percentile_cont(0.2 + x) + Failed to bind expression: percentile_cont(0.2 + x) WITHIN GROUP (ORDER BY y) Caused by: Feature is not yet implemented: variable as direct argument of ordered-set aggregate @@ -1656,7 +1656,7 @@ create table t (x int, y varchar); select mode(1) within group (order by y desc) from t; binder_error: | - Failed to bind expression: mode(1) + Failed to bind expression: mode(1) WITHIN GROUP (ORDER BY y DESC) Caused by: Invalid input syntax: invalid direct args or within group argument for `mode` aggregation diff --git a/src/frontend/planner_test/tests/testdata/output/expr.yaml b/src/frontend/planner_test/tests/testdata/output/expr.yaml index d24d0f0eeba18..3ddc9d8fafc72 100644 --- a/src/frontend/planner_test/tests/testdata/output/expr.yaml +++ b/src/frontend/planner_test/tests/testdata/output/expr.yaml @@ -558,7 +558,7 @@ create table t (v1 timestamp with time zone, v2 int); select sum(v2) filter (where v1 >= now()) as sum_v2 from t; stream_error: | - Failed to bind expression: sum(v2) FILTER(WHERE v1 >= now()) + Failed to bind expression: sum(v2) FILTER (WHERE v1 >= now()) Caused by: Invalid input syntax: For streaming queries, `NOW()` function is only allowed in `WHERE`, `HAVING`, `ON` and `FROM`. Found in clause: Some(Filter). Please please refer to https://www.risingwave.dev/docs/current/sql-pattern-temporal-filters/ for more information diff --git a/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml b/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml index 3449606ad97ba..fe98be90cc664 100644 --- a/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml +++ b/src/frontend/planner_test/tests/testdata/output/over_window_function.yaml @@ -50,7 +50,7 @@ create table t(x int); select sum(x) filter (where row_number() over () > 1) from t; binder_error: | - Failed to bind expression: sum(x) FILTER(WHERE row_number() OVER () > 1) + Failed to bind expression: sum(x) FILTER (WHERE row_number() OVER () > 1) Caused by: Invalid input syntax: window functions are not allowed in FILTER diff --git a/src/frontend/planner_test/tests/testdata/output/project_set.yaml b/src/frontend/planner_test/tests/testdata/output/project_set.yaml index 8373ea5187fca..c4102e164d7e8 100644 --- a/src/frontend/planner_test/tests/testdata/output/project_set.yaml +++ b/src/frontend/planner_test/tests/testdata/output/project_set.yaml @@ -228,7 +228,7 @@ sql: | with a(a1) as (values (array[2]), (null)) select count(*) filter(where unnest(a1) < 3) from a; binder_error: | - Failed to bind expression: count(*) FILTER(WHERE unnest(a1) < 3) + Failed to bind expression: count(*) FILTER (WHERE unnest(a1) < 3) Caused by: Invalid input syntax: table functions are not allowed in FILTER diff --git a/src/frontend/src/binder/mod.rs b/src/frontend/src/binder/mod.rs index 8e96a5d177a12..ebbf965d55e72 100644 --- a/src/frontend/src/binder/mod.rs +++ b/src/frontend/src/binder/mod.rs @@ -816,8 +816,6 @@ mod tests { order_by: [], ignore_nulls: false, }, - over: None, - filter: None, within_group: Some( OrderByExpr { expr: Identifier( @@ -830,6 +828,8 @@ mod tests { nulls_first: None, }, ), + filter: None, + over: None, }, ), ), diff --git a/src/sqlparser/src/ast/mod.rs b/src/sqlparser/src/ast/mod.rs index eeb9859679717..3df7b753147ca 100644 --- a/src/sqlparser/src/ast/mod.rs +++ b/src/sqlparser/src/ast/mod.rs @@ -2567,13 +2567,19 @@ impl FunctionArgList { #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Function { - /// Whether the function is prefixed with `aggregate:` + /// Whether the function is prefixed with `AGGREGATE:` pub scalar_as_agg: bool, + /// Function name. pub name: ObjectName, + /// Argument list of the function call, i.e. things in `()`. pub arg_list: FunctionArgList, - pub over: Option, - pub filter: Option>, + /// `WITHIN GROUP` clause of the function call, for ordered-set aggregate functions. + /// FIXME(rc): why we only support one expression here? pub within_group: Option>, + /// `FILTER` clause of the function call, for aggregate and window (not supported yet) functions. + pub filter: Option>, + /// `OVER` clause of the function call, for window functions. + pub over: Option, } impl Function { @@ -2582,9 +2588,9 @@ impl Function { scalar_as_agg: false, name, arg_list: FunctionArgList::empty(), - over: None, - filter: None, within_group: None, + filter: None, + over: None, } } } @@ -2595,11 +2601,14 @@ impl fmt::Display for Function { write!(f, "AGGREGATE:")?; } write!(f, "{}{}", self.name, self.arg_list)?; - if let Some(o) = &self.over { - write!(f, " OVER ({})", o)?; + if let Some(within_group) = &self.within_group { + write!(f, " WITHIN GROUP (ORDER BY {})", within_group)?; } if let Some(filter) = &self.filter { - write!(f, " FILTER(WHERE {})", filter)?; + write!(f, " FILTER (WHERE {})", filter)?; + } + if let Some(o) = &self.over { + write!(f, " OVER ({})", o)?; } Ok(()) } diff --git a/src/sqlparser/src/parser.rs b/src/sqlparser/src/parser.rs index e239bfb3b0a6f..5e7df3c2fc53a 100644 --- a/src/sqlparser/src/parser.rs +++ b/src/sqlparser/src/parser.rs @@ -824,6 +824,27 @@ impl Parser<'_> { }; let name = self.parse_object_name()?; let arg_list = self.parse_argument_list()?; + + let within_group = if self.parse_keywords(&[Keyword::WITHIN, Keyword::GROUP]) { + self.expect_token(&Token::LParen)?; + self.expect_keywords(&[Keyword::ORDER, Keyword::BY])?; + let order_by = self.parse_order_by_expr()?; + self.expect_token(&Token::RParen)?; + Some(Box::new(order_by.clone())) + } else { + None + }; + + let filter = if self.parse_keyword(Keyword::FILTER) { + self.expect_token(&Token::LParen)?; + self.expect_keyword(Keyword::WHERE)?; + let filter_expr = self.parse_expr()?; + self.expect_token(&Token::RParen)?; + Some(Box::new(filter_expr)) + } else { + None + }; + let over = if self.parse_keyword(Keyword::OVER) { // TODO: support window names (`OVER mywin`) in place of inline specification self.expect_token(&Token::LParen)?; @@ -855,33 +876,13 @@ impl Parser<'_> { None }; - let filter = if self.parse_keyword(Keyword::FILTER) { - self.expect_token(&Token::LParen)?; - self.expect_keyword(Keyword::WHERE)?; - let filter_expr = self.parse_expr()?; - self.expect_token(&Token::RParen)?; - Some(Box::new(filter_expr)) - } else { - None - }; - - let within_group = if self.parse_keywords(&[Keyword::WITHIN, Keyword::GROUP]) { - self.expect_token(&Token::LParen)?; - self.expect_keywords(&[Keyword::ORDER, Keyword::BY])?; - let order_by = self.parse_order_by_expr()?; - self.expect_token(&Token::RParen)?; - Some(Box::new(order_by.clone())) - } else { - None - }; - Ok(Expr::Function(Function { scalar_as_agg, name, arg_list, - over, - filter, within_group, + filter, + over, })) } diff --git a/src/sqlparser/tests/sqlparser_common.rs b/src/sqlparser/tests/sqlparser_common.rs index 46486419c629a..d63747fa20e5a 100644 --- a/src/sqlparser/tests/sqlparser_common.rs +++ b/src/sqlparser/tests/sqlparser_common.rs @@ -350,9 +350,9 @@ fn parse_select_count_wildcard() { arg_list: FunctionArgList::args_only(vec![FunctionArg::Unnamed( FunctionArgExpr::Wildcard(None) )]), - over: None, - filter: None, within_group: None, + filter: None, + over: None, }), expr_from_projection(only(&select.projection)) ); @@ -374,9 +374,9 @@ fn parse_select_count_distinct() { }))], vec![] ), - over: None, - filter: None, within_group: None, + filter: None, + over: None, }), expr_from_projection(only(&select.projection)) ); @@ -1169,9 +1169,9 @@ fn parse_select_having() { arg_list: FunctionArgList::args_only(vec![FunctionArg::Unnamed( FunctionArgExpr::Wildcard(None) )]), - over: None, - filter: None, within_group: None, + filter: None, + over: None, })), op: BinaryOperator::Gt, right: Box::new(Expr::Value(number("1"))), @@ -1921,9 +1921,9 @@ fn parse_named_argument_function() { ))), }, ]), - over: None, - filter: None, within_group: None, + filter: None, + over: None, }), expr_from_projection(only(&select.projection)) ); @@ -1931,23 +1931,39 @@ fn parse_named_argument_function() { #[test] fn parse_window_functions() { - let sql = "SELECT row_number() OVER (ORDER BY dt DESC), \ - sum(foo) OVER (PARTITION BY a, b ORDER BY c, d \ - ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), \ - avg(bar) OVER (ORDER BY a \ - RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING), \ - max(baz) OVER (ORDER BY a \ - ROWS UNBOUNDED PRECEDING), \ - sum(qux) OVER (ORDER BY a \ - GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) \ - FROM foo"; + let sql = "\ + SELECT \ + row_number() OVER (ORDER BY dt DESC), \ + sum(foo) OVER (\ + PARTITION BY a, b \ + ORDER BY c, d \ + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\ + ), \ + avg(bar) OVER (\ + ORDER BY a \ + RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING\ + ), \ + max(baz) OVER (\ + ORDER BY a \ + ROWS UNBOUNDED PRECEDING\ + ), \ + sum(qux) OVER (\ + ORDER BY a \ + GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING\ + ), \ + AGGREGATE:my_func(abc) OVER (), \ + rank(foo IGNORE NULLS) FILTER (WHERE bar > 0) OVER (ORDER BY a) \ + FROM foo\ + "; let select = verified_only_select(sql); - assert_eq!(5, select.projection.len()); + assert_eq!(7, select.projection.len()); assert_eq!( &Expr::Function(Function { scalar_as_agg: false, name: ObjectName(vec![Ident::new_unchecked("row_number")]), arg_list: FunctionArgList::empty(), + within_group: None, + filter: None, over: Some(WindowSpec { partition_by: vec![], order_by: vec![OrderByExpr { @@ -1957,10 +1973,62 @@ fn parse_window_functions() { }], window_frame: None, }), + }), + expr_from_projection(&select.projection[0]) + ); + assert_eq!( + &Expr::Function(Function { + scalar_as_agg: true, + name: ObjectName(vec![Ident::new_unchecked("my_func")]), + arg_list: FunctionArgList { + distinct: false, + args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( + Expr::Identifier(Ident::new_unchecked("abc")) + ))], + variadic: false, + order_by: vec![], + ignore_nulls: false, + }, + within_group: None, filter: None, + over: Some(WindowSpec { + partition_by: vec![], + order_by: vec![], + window_frame: None, + }), + }), + expr_from_projection(&select.projection[5]) + ); + assert_eq!( + &Expr::Function(Function { + scalar_as_agg: false, + name: ObjectName(vec![Ident::new_unchecked("rank")]), + arg_list: FunctionArgList { + distinct: false, + args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr( + Expr::Identifier(Ident::new_unchecked("foo")) + ))], + variadic: false, + order_by: vec![], + ignore_nulls: true, + }, within_group: None, + filter: Some(Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new_unchecked("bar"))), + op: BinaryOperator::Gt, + right: Box::new(Expr::Value(Value::Number("0".to_string()))), + })), + over: Some(WindowSpec { + partition_by: vec![], + order_by: vec![OrderByExpr { + expr: Expr::Identifier(Ident::new_unchecked("a")), + asc: None, + nulls_first: None, + }], + window_frame: None, + }), }), - expr_from_projection(&select.projection[0]) + expr_from_projection(&select.projection[6]) ); } @@ -2002,9 +2070,9 @@ fn parse_aggregate_with_order_by() { } ] ), - over: None, - filter: None, within_group: None, + filter: None, + over: None, }), expr_from_projection(only(&select.projection)) ); @@ -2012,7 +2080,7 @@ fn parse_aggregate_with_order_by() { #[test] fn parse_aggregate_with_filter() { - let sql = "SELECT sum(a) FILTER(WHERE (a > 0) AND (a IS NOT NULL)) FROM foo"; + let sql = "SELECT sum(a) FILTER (WHERE (a > 0) AND (a IS NOT NULL)) FROM foo"; let select = verified_only_select(sql); assert_eq!( &Expr::Function(Function { @@ -2021,7 +2089,7 @@ fn parse_aggregate_with_filter() { arg_list: FunctionArgList::args_only(vec![FunctionArg::Unnamed( FunctionArgExpr::Expr(Expr::Identifier(Ident::new_unchecked("a"))) )]), - over: None, + within_group: None, filter: Some(Box::new(Expr::BinaryOp { left: Box::new(Expr::Nested(Box::new(Expr::BinaryOp { left: Box::new(Expr::Identifier(Ident::new_unchecked("a"))), @@ -2033,7 +2101,35 @@ fn parse_aggregate_with_filter() { Expr::Identifier(Ident::new_unchecked("a")) ))))) })), - within_group: None, + over: None, + }), + expr_from_projection(only(&select.projection)), + ); +} + +#[test] +fn parse_aggregate_with_within_group() { + let sql = + "SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY a DESC NULLS FIRST) FILTER (WHERE b > 0) FROM foo"; + let select = verified_only_select(sql); + assert_eq!( + &Expr::Function(Function { + scalar_as_agg: false, + name: ObjectName(vec![Ident::new_unchecked("percentile_cont")]), + arg_list: FunctionArgList::args_only(vec![FunctionArg::Unnamed( + FunctionArgExpr::Expr(Expr::Value(Value::Number("0.5".to_string()))) + )]), + within_group: Some(Box::new(OrderByExpr { + expr: Expr::Identifier(Ident::new_unchecked("a")), + asc: Some(false), + nulls_first: Some(true), + },)), + filter: Some(Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new_unchecked("b"))), + op: BinaryOperator::Gt, + right: Box::new(Expr::Value(Value::Number("0".to_string()))) + })), + over: None, }), expr_from_projection(only(&select.projection)), ); @@ -2274,9 +2370,9 @@ fn parse_delimited_identifiers() { scalar_as_agg: false, name: ObjectName(vec![Ident::with_quote_unchecked('"', "myfun")]), arg_list: FunctionArgList::empty(), - over: None, - filter: None, within_group: None, + filter: None, + over: None, }), expr_from_projection(&select.projection[1]), ); diff --git a/src/sqlparser/tests/testdata/lambda.yaml b/src/sqlparser/tests/testdata/lambda.yaml index eceb53af280bd..975607eac6b25 100644 --- a/src/sqlparser/tests/testdata/lambda.yaml +++ b/src/sqlparser/tests/testdata/lambda.yaml @@ -1,10 +1,10 @@ # This file is automatically generated by `src/sqlparser/tests/parser_test.rs`. - input: select array_transform(array[1,2,3], |x| x * 2) formatted_sql: SELECT array_transform(ARRAY[1, 2, 3], |x| x * 2) - formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "array_transform", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Array(Array { elem: [Value(Number("1")), Value(Number("2")), Value(Number("3"))], named: true }))), Unnamed(Expr(LambdaFunction { args: [Ident { value: "x", quote_style: None }], body: BinaryOp { left: Identifier(Ident { value: "x", quote_style: None }), op: Multiply, right: Value(Number("2")) } }))], variadic: false, order_by: [], ignore_nulls: false }, over: None, filter: None, within_group: None }))], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' + formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "array_transform", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Array(Array { elem: [Value(Number("1")), Value(Number("2")), Value(Number("3"))], named: true }))), Unnamed(Expr(LambdaFunction { args: [Ident { value: "x", quote_style: None }], body: BinaryOp { left: Identifier(Ident { value: "x", quote_style: None }), op: Multiply, right: Value(Number("2")) } }))], variadic: false, order_by: [], ignore_nulls: false }, within_group: None, filter: None, over: None }))], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: select array_transform(array[], |s| case when s ilike 'apple%' then 'apple' when s ilike 'google%' then 'google' else 'unknown' end) formatted_sql: SELECT array_transform(ARRAY[], |s| CASE WHEN s ILIKE 'apple%' THEN 'apple' WHEN s ILIKE 'google%' THEN 'google' ELSE 'unknown' END) - formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "array_transform", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Array(Array { elem: [], named: true }))), Unnamed(Expr(LambdaFunction { args: [Ident { value: "s", quote_style: None }], body: Case { operand: None, conditions: [ILike { negated: false, expr: Identifier(Ident { value: "s", quote_style: None }), pattern: Value(SingleQuotedString("apple%")), escape_char: None }, ILike { negated: false, expr: Identifier(Ident { value: "s", quote_style: None }), pattern: Value(SingleQuotedString("google%")), escape_char: None }], results: [Value(SingleQuotedString("apple")), Value(SingleQuotedString("google"))], else_result: Some(Value(SingleQuotedString("unknown"))) } }))], variadic: false, order_by: [], ignore_nulls: false }, over: None, filter: None, within_group: None }))], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' + formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "array_transform", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Array(Array { elem: [], named: true }))), Unnamed(Expr(LambdaFunction { args: [Ident { value: "s", quote_style: None }], body: Case { operand: None, conditions: [ILike { negated: false, expr: Identifier(Ident { value: "s", quote_style: None }), pattern: Value(SingleQuotedString("apple%")), escape_char: None }, ILike { negated: false, expr: Identifier(Ident { value: "s", quote_style: None }), pattern: Value(SingleQuotedString("google%")), escape_char: None }], results: [Value(SingleQuotedString("apple")), Value(SingleQuotedString("google"))], else_result: Some(Value(SingleQuotedString("unknown"))) } }))], variadic: false, order_by: [], ignore_nulls: false }, within_group: None, filter: None, over: None }))], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: select array_transform(array[], |x, y| x + y * 2) formatted_sql: SELECT array_transform(ARRAY[], |x, y| x + y * 2) - formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "array_transform", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Array(Array { elem: [], named: true }))), Unnamed(Expr(LambdaFunction { args: [Ident { value: "x", quote_style: None }, Ident { value: "y", quote_style: None }], body: BinaryOp { left: Identifier(Ident { value: "x", quote_style: None }), op: Plus, right: BinaryOp { left: Identifier(Ident { value: "y", quote_style: None }), op: Multiply, right: Value(Number("2")) } } }))], variadic: false, order_by: [], ignore_nulls: false }, over: None, filter: None, within_group: None }))], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' + formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "array_transform", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Array(Array { elem: [], named: true }))), Unnamed(Expr(LambdaFunction { args: [Ident { value: "x", quote_style: None }, Ident { value: "y", quote_style: None }], body: BinaryOp { left: Identifier(Ident { value: "x", quote_style: None }), op: Plus, right: BinaryOp { left: Identifier(Ident { value: "y", quote_style: None }), op: Multiply, right: Value(Number("2")) } } }))], variadic: false, order_by: [], ignore_nulls: false }, within_group: None, filter: None, over: None }))], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' diff --git a/src/sqlparser/tests/testdata/qualified_operator.yaml b/src/sqlparser/tests/testdata/qualified_operator.yaml index ddab7deeb214f..bd06390dc8924 100644 --- a/src/sqlparser/tests/testdata/qualified_operator.yaml +++ b/src/sqlparser/tests/testdata/qualified_operator.yaml @@ -19,10 +19,10 @@ formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Identifier(Ident { value: "operator", quote_style: None }))], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: select "operator"(foo.bar); formatted_sql: SELECT "operator"(foo.bar) - formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "operator", quote_style: Some(''"'') }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(CompoundIdentifier([Ident { value: "foo", quote_style: None }, Ident { value: "bar", quote_style: None }])))], variadic: false, order_by: [], ignore_nulls: false }, over: None, filter: None, within_group: None }))], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' + formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "operator", quote_style: Some(''"'') }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(CompoundIdentifier([Ident { value: "foo", quote_style: None }, Ident { value: "bar", quote_style: None }])))], variadic: false, order_by: [], ignore_nulls: false }, within_group: None, filter: None, over: None }))], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: select operator operator(+) operator(+) "operator"(9) operator from operator; formatted_sql: SELECT operator OPERATOR(+) OPERATOR(+) "operator"(9) AS operator FROM operator - formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [ExprWithAlias { expr: BinaryOp { left: Identifier(Ident { value: "operator", quote_style: None }), op: PGQualified(QualifiedOperator { schema: None, name: "+" }), right: UnaryOp { op: PGQualified(QualifiedOperator { schema: None, name: "+" }), expr: Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "operator", quote_style: Some(''"'') }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Value(Number("9"))))], variadic: false, order_by: [], ignore_nulls: false }, over: None, filter: None, within_group: None }) } }, alias: Ident { value: "operator", quote_style: None } }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident { value: "operator", quote_style: None }]), alias: None, as_of: None }, joins: [] }], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' + formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [ExprWithAlias { expr: BinaryOp { left: Identifier(Ident { value: "operator", quote_style: None }), op: PGQualified(QualifiedOperator { schema: None, name: "+" }), right: UnaryOp { op: PGQualified(QualifiedOperator { schema: None, name: "+" }), expr: Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "operator", quote_style: Some(''"'') }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Value(Number("9"))))], variadic: false, order_by: [], ignore_nulls: false }, within_group: None, filter: None, over: None }) } }, alias: Ident { value: "operator", quote_style: None } }], from: [TableWithJoins { relation: Table { name: ObjectName([Ident { value: "operator", quote_style: None }]), alias: None, as_of: None }, joins: [] }], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: select 3 operator(-) 2 - 1; formatted_sql: SELECT 3 OPERATOR(-) 2 - 1 formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(BinaryOp { left: Value(Number("3")), op: PGQualified(QualifiedOperator { schema: None, name: "-" }), right: BinaryOp { left: Value(Number("2")), op: Minus, right: Value(Number("1")) } })], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' diff --git a/src/sqlparser/tests/testdata/select.yaml b/src/sqlparser/tests/testdata/select.yaml index 83c624c64309b..606b3d7a0c13f 100644 --- a/src/sqlparser/tests/testdata/select.yaml +++ b/src/sqlparser/tests/testdata/select.yaml @@ -1,7 +1,7 @@ # This file is automatically generated by `src/sqlparser/tests/parser_test.rs`. - input: SELECT sqrt(id) FROM foo formatted_sql: SELECT sqrt(id) FROM foo - formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "sqrt", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Identifier(Ident { value: "id", quote_style: None })))], variadic: false, order_by: [], ignore_nulls: false }, over: None, filter: None, within_group: None }))], from: [TableWithJoins { relation: Table { name: ObjectName([Ident { value: "foo", quote_style: None }]), alias: None, as_of: None }, joins: [] }], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' + formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "sqrt", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Identifier(Ident { value: "id", quote_style: None })))], variadic: false, order_by: [], ignore_nulls: false }, within_group: None, filter: None, over: None }))], from: [TableWithJoins { relation: Table { name: ObjectName([Ident { value: "foo", quote_style: None }]), alias: None, as_of: None }, joins: [] }], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: SELECT INT '1' formatted_sql: SELECT INT '1' - input: SELECT (foo).v1.v2 FROM foo @@ -99,7 +99,7 @@ formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(AtTimeZone { timestamp: TypedString { data_type: Timestamp(true), value: "2022-10-01 12:00:00Z" }, time_zone: Identifier(Ident { value: "zone", quote_style: None }) })], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: SELECT now() + INTERVAL '14 days' AT TIME ZONE 'UTC'; -- https://www.postgresql.org/message-id/CADT4RqBPdbsZW7HS1jJP319TMRHs1hzUiP=iRJYR6UqgHCrgNQ@mail.gmail.com formatted_sql: SELECT now() + INTERVAL '14 days' AT TIME ZONE 'UTC' - formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(BinaryOp { left: Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "now", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [], variadic: false, order_by: [], ignore_nulls: false }, over: None, filter: None, within_group: None }), op: Plus, right: AtTimeZone { timestamp: Value(Interval { value: "14 days", leading_field: None, leading_precision: None, last_field: None, fractional_seconds_precision: None }), time_zone: Value(SingleQuotedString("UTC")) } })], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' + formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(BinaryOp { left: Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "now", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [], variadic: false, order_by: [], ignore_nulls: false }, within_group: None, filter: None, over: None }), op: Plus, right: AtTimeZone { timestamp: Value(Interval { value: "14 days", leading_field: None, leading_precision: None, last_field: None, fractional_seconds_precision: None }), time_zone: Value(SingleQuotedString("UTC")) } })], from: [], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: SELECT c FROM t WHERE c >= '2019-03-27T22:00:00.000Z'::timestamp AT TIME ZONE 'Europe/Brussels'; -- https://github.com/sqlparser-rs/sqlparser-rs/issues/1266 formatted_sql: SELECT c FROM t WHERE c >= CAST('2019-03-27T22:00:00.000Z' AS TIMESTAMP) AT TIME ZONE 'Europe/Brussels' formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Identifier(Ident { value: "c", quote_style: None }))], from: [TableWithJoins { relation: Table { name: ObjectName([Ident { value: "t", quote_style: None }]), alias: None, as_of: None }, joins: [] }], lateral_views: [], selection: Some(BinaryOp { left: Identifier(Ident { value: "c", quote_style: None }), op: GtEq, right: AtTimeZone { timestamp: Cast { expr: Value(SingleQuotedString("2019-03-27T22:00:00.000Z")), data_type: Timestamp(false) }, time_zone: Value(SingleQuotedString("Europe/Brussels")) } }), group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' @@ -172,8 +172,8 @@ formatted_sql: SELECT id1, a1, id2, a2 FROM stream AS S JOIN version FOR SYSTEM_TIME AS OF PROCTIME() AS V ON id1 = id2 formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Identifier(Ident { value: "id1", quote_style: None })), UnnamedExpr(Identifier(Ident { value: "a1", quote_style: None })), UnnamedExpr(Identifier(Ident { value: "id2", quote_style: None })), UnnamedExpr(Identifier(Ident { value: "a2", quote_style: None }))], from: [TableWithJoins { relation: Table { name: ObjectName([Ident { value: "stream", quote_style: None }]), alias: Some(TableAlias { name: Ident { value: "S", quote_style: None }, columns: [] }), as_of: None }, joins: [Join { relation: Table { name: ObjectName([Ident { value: "version", quote_style: None }]), alias: Some(TableAlias { name: Ident { value: "V", quote_style: None }, columns: [] }), as_of: Some(ProcessTime) }, join_operator: Inner(On(BinaryOp { left: Identifier(Ident { value: "id1", quote_style: None }), op: Eq, right: Identifier(Ident { value: "id2", quote_style: None }) })) }] }], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: select percentile_cont(0.3) within group (order by x desc) from unnest(array[1,2,4,5,10]) as x - formatted_sql: SELECT percentile_cont(0.3) FROM unnest(ARRAY[1, 2, 4, 5, 10]) AS x - formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "percentile_cont", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Value(Number("0.3"))))], variadic: false, order_by: [], ignore_nulls: false }, over: None, filter: None, within_group: Some(OrderByExpr { expr: Identifier(Ident { value: "x", quote_style: None }), asc: Some(false), nulls_first: None }) }))], from: [TableWithJoins { relation: TableFunction { name: ObjectName([Ident { value: "unnest", quote_style: None }]), alias: Some(TableAlias { name: Ident { value: "x", quote_style: None }, columns: [] }), args: [Unnamed(Expr(Array(Array { elem: [Value(Number("1")), Value(Number("2")), Value(Number("4")), Value(Number("5")), Value(Number("10"))], named: true })))], with_ordinality: false }, joins: [] }], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' + formatted_sql: SELECT percentile_cont(0.3) WITHIN GROUP (ORDER BY x DESC) FROM unnest(ARRAY[1, 2, 4, 5, 10]) AS x + formatted_ast: 'Query(Query { with: None, body: Select(Select { distinct: All, projection: [UnnamedExpr(Function(Function { scalar_as_agg: false, name: ObjectName([Ident { value: "percentile_cont", quote_style: None }]), arg_list: FunctionArgList { distinct: false, args: [Unnamed(Expr(Value(Number("0.3"))))], variadic: false, order_by: [], ignore_nulls: false }, within_group: Some(OrderByExpr { expr: Identifier(Ident { value: "x", quote_style: None }), asc: Some(false), nulls_first: None }), filter: None, over: None }))], from: [TableWithJoins { relation: TableFunction { name: ObjectName([Ident { value: "unnest", quote_style: None }]), alias: Some(TableAlias { name: Ident { value: "x", quote_style: None }, columns: [] }), args: [Unnamed(Expr(Array(Array { elem: [Value(Number("1")), Value(Number("2")), Value(Number("4")), Value(Number("5")), Value(Number("10"))], named: true })))], with_ordinality: false }, joins: [] }], lateral_views: [], selection: None, group_by: [], having: None }), order_by: [], limit: None, offset: None, fetch: None })' - input: select percentile_cont(0.3) within group (order by x, y desc) from t error_msg: |- sql parser error: expected ), found: ,