diff --git a/src/query/ast/src/ast/format/ast_format.rs b/src/query/ast/src/ast/format/ast_format.rs index 7ff26add24c9c..7f4e16c8c7b3c 100644 --- a/src/query/ast/src/ast/format/ast_format.rs +++ b/src/query/ast/src/ast/format/ast_format.rs @@ -2777,6 +2777,16 @@ impl<'ast> Visitor<'ast> for AstFormatVisitor { children.push(window_list_node); } + if let Some(qualify) = &stmt.qualify { + self.visit_expr(qualify); + let qualify_child = self.children.pop().unwrap(); + let qualify_name = "Qualify".to_string(); + let qualify_format_ctx = AstFormatContext::with_children(qualify_name, 1); + let qualify_node = + FormatTreeNode::with_children(qualify_format_ctx, vec![qualify_child]); + children.push(qualify_node); + } + let name = "SelectQuery".to_string(); let format_ctx = AstFormatContext::with_children(name, children.len()); let node = FormatTreeNode::with_children(format_ctx, children); diff --git a/src/query/ast/src/ast/query.rs b/src/query/ast/src/ast/query.rs index 8edd507dc13dd..cc029d61d4872 100644 --- a/src/query/ast/src/ast/query.rs +++ b/src/query/ast/src/ast/query.rs @@ -92,6 +92,8 @@ pub struct SelectStmt { pub having: Option, // `WINDOW` clause pub window_list: Option>, + // `QUALIFY` clause + pub qualify: Option, } /// Group by Clause. diff --git a/src/query/ast/src/parser/query.rs b/src/query/ast/src/parser/query.rs index 67de78a59d91c..dbeb1f1b131a5 100644 --- a/src/query/ast/src/parser/query.rs +++ b/src/query/ast/src/parser/query.rs @@ -58,6 +58,7 @@ pub enum SetOperationElement { group_by: Option, having: Box>, window_list: Option>, + qualify: Box>, }, SetOperation { op: SetOperator, @@ -104,6 +105,7 @@ pub fn set_operation_element(i: Input) -> IResult> ~ ( GROUP ~ ^BY ~ ^#group_by_items )? ~ ( HAVING ~ ^#expr )? ~ ( WINDOW ~ ^#comma_separated_list1(window_clause) )? + ~ ( QUALIFY ~ ^#expr )? }, |( _select, @@ -115,6 +117,7 @@ pub fn set_operation_element(i: Input) -> IResult> opt_group_by_block, opt_having_block, opt_window_block, + opt_qualify_block, )| { SetOperationElement::SelectStmt { hints: opt_hints, @@ -129,6 +132,7 @@ pub fn set_operation_element(i: Input) -> IResult> group_by: opt_group_by_block.map(|(_, _, group_by)| group_by), having: Box::new(opt_having_block.map(|(_, having)| having)), window_list: opt_window_block.map(|(_, windows)| windows), + qualify: Box::new(opt_qualify_block.map(|(_, qualify)| qualify)), } }, ); @@ -142,6 +146,7 @@ pub fn set_operation_element(i: Input) -> IResult> ~ ( GROUP ~ ^BY ~ ^#group_by_items )? ~ ( HAVING ~ ^#expr )? ~ ( WINDOW ~ ^#comma_separated_list1(window_clause) )? + ~ ( QUALIFY ~ ^#expr )? }, |( opt_from_block, @@ -153,6 +158,7 @@ pub fn set_operation_element(i: Input) -> IResult> opt_group_by_block, opt_having_block, opt_window_block, + opt_qualify_block, )| { SetOperationElement::SelectStmt { hints: opt_hints, @@ -167,6 +173,7 @@ pub fn set_operation_element(i: Input) -> IResult> group_by: opt_group_by_block.map(|(_, _, group_by)| group_by), having: Box::new(opt_having_block.map(|(_, having)| having)), window_list: opt_window_block.map(|(_, windows)| windows), + qualify: Box::new(opt_qualify_block.map(|(_, qualify)| qualify)), } }, ); @@ -262,6 +269,7 @@ impl<'a, I: Iterator>> PrattParser group_by, having, window_list, + qualify, } => SetExpr::Select(Box::new(SelectStmt { span: transform_span(input.span.0), hints, @@ -272,6 +280,7 @@ impl<'a, I: Iterator>> PrattParser group_by, having: *having, window_list, + qualify: *qualify, })), SetOperationElement::Values(values) => SetExpr::Values { span: transform_span(input.span.0), diff --git a/src/query/ast/src/parser/token.rs b/src/query/ast/src/parser/token.rs index 4e694f114621b..3cffd758522a4 100644 --- a/src/query/ast/src/parser/token.rs +++ b/src/query/ast/src/parser/token.rs @@ -807,6 +807,8 @@ pub enum TokenKind { PRESIGN, #[token("PRIVILEGES", ignore(ascii_case))] PRIVILEGES, + #[token("QUALIFY", ignore(ascii_case))] + QUALIFY, #[token("REMOVE", ignore(ascii_case))] REMOVE, #[token("RETAIN", ignore(ascii_case))] @@ -1301,6 +1303,7 @@ impl TokenKind { | TokenKind::OF | TokenKind::ORDER | TokenKind::OVER + | TokenKind::QUALIFY | TokenKind::ROWS // | TokenKind::PRECISION // | TokenKind::RETURNING @@ -1421,6 +1424,7 @@ impl TokenKind { | TokenKind::ORDER | TokenKind::OVER | TokenKind::PARTITION + | TokenKind::QUALIFY | TokenKind::ROWS | TokenKind::RANGE // | TokenKind::OVERLAPS diff --git a/src/query/ast/src/visitors/visitor.rs b/src/query/ast/src/visitors/visitor.rs index dc814041d31d6..4551e0498dcc5 100644 --- a/src/query/ast/src/visitors/visitor.rs +++ b/src/query/ast/src/visitors/visitor.rs @@ -646,6 +646,8 @@ pub trait Visitor<'ast>: Sized { selection, group_by, having, + window_list, + qualify, .. } = stmt; @@ -680,6 +682,16 @@ pub trait Visitor<'ast>: Sized { if let Some(having) = having { walk_expr(self, having); } + + if let Some(window_list) = window_list { + for window_def in window_list { + walk_window_definition(self, window_def); + } + } + + if let Some(qualify) = qualify { + walk_expr(self, qualify); + } } fn visit_select_target(&mut self, target: &'ast SelectTarget) { diff --git a/src/query/ast/src/visitors/visitor_mut.rs b/src/query/ast/src/visitors/visitor_mut.rs index 5c417f67cabed..8fc5f32e0fbba 100644 --- a/src/query/ast/src/visitors/visitor_mut.rs +++ b/src/query/ast/src/visitors/visitor_mut.rs @@ -28,6 +28,7 @@ use super::walk_mut::walk_statement_mut; use super::walk_mut::walk_table_reference_mut; use super::walk_stream_point_mut; use super::walk_time_travel_point_mut; +use super::walk_window_definition_mut; use crate::ast::*; use crate::visitors::walk_column_id_mut; @@ -659,6 +660,8 @@ pub trait VisitorMut: Sized { selection, group_by, having, + window_list, + qualify, .. } = stmt; @@ -693,6 +696,16 @@ pub trait VisitorMut: Sized { if let Some(having) = having { Self::visit_expr(self, having); } + + if let Some(window_list) = window_list { + for window_def in window_list { + walk_window_definition_mut(self, window_def); + } + } + + if let Some(qualify) = qualify { + Self::visit_expr(self, qualify); + } } fn visit_select_target(&mut self, target: &mut SelectTarget) { diff --git a/src/query/ast/src/visitors/walk_mut.rs b/src/query/ast/src/visitors/walk_mut.rs index a0379f0fa0772..673b9a030030e 100644 --- a/src/query/ast/src/visitors/walk_mut.rs +++ b/src/query/ast/src/visitors/walk_mut.rs @@ -198,6 +198,35 @@ pub fn walk_set_expr_mut(visitor: &mut V, set_expr: &mut SetExpr) } } +pub fn walk_window_definition_mut( + visitor: &mut V, + window_definition: &mut WindowDefinition, +) { + let WindowDefinition { name, spec: window } = window_definition; + + visitor.visit_identifier(name); + + let WindowSpec { + partition_by, + order_by, + window_frame, + .. + } = window; + + for expr in partition_by { + visitor.visit_expr(expr); + } + + for order_by in order_by { + visitor.visit_order_by(order_by); + } + + if let Some(frame) = window_frame { + visitor.visit_frame_bound(&mut frame.start_bound); + visitor.visit_frame_bound(&mut frame.end_bound); + } +} + pub fn walk_select_target_mut(visitor: &mut V, target: &mut SelectTarget) { match target { SelectTarget::AliasedExpr { expr, alias } => { diff --git a/src/query/ast/tests/it/testdata/query-error.txt b/src/query/ast/tests/it/testdata/query-error.txt index 1ca0c1963a0eb..bd09b7c29256c 100644 --- a/src/query/ast/tests/it/testdata/query-error.txt +++ b/src/query/ast/tests/it/testdata/query-error.txt @@ -79,6 +79,6 @@ error: --> SQL:1:10 | 1 | select 1 1 - | ^ unexpected `1`, expecting , , `AS`, `,`, `FROM`, `WHERE`, `GROUP`, `HAVING`, `WINDOW`, `(`, `WITH`, `UNION`, `EXCEPT`, `INTERSECT`, `SELECT`, `VALUES`, `ORDER`, `LIMIT`, `OFFSET`, or `IGNORE_RESULT` + | ^ unexpected `1`, expecting , , `AS`, `,`, `FROM`, `WHERE`, `GROUP`, `HAVING`, `WINDOW`, `QUALIFY`, `(`, `WITH`, `UNION`, `EXCEPT`, `INTERSECT`, `SELECT`, `VALUES`, `ORDER`, `LIMIT`, `OFFSET`, or `IGNORE_RESULT` diff --git a/src/query/ast/tests/it/testdata/query.txt b/src/query/ast/tests/it/testdata/query.txt index 2960ab2baf841..637cd41325671 100644 --- a/src/query/ast/tests/it/testdata/query.txt +++ b/src/query/ast/tests/it/testdata/query.txt @@ -174,6 +174,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -355,6 +356,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -444,6 +446,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -533,6 +536,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -661,6 +665,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -789,6 +794,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -926,6 +932,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1033,6 +1040,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1159,6 +1167,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1264,6 +1273,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1374,6 +1384,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1479,6 +1490,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1589,6 +1601,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1686,6 +1699,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1796,6 +1810,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1901,6 +1916,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -1972,6 +1988,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -2083,6 +2100,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -2283,6 +2301,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -2395,6 +2414,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -2449,6 +2469,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -2522,6 +2543,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -2735,6 +2757,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -3137,6 +3160,7 @@ Query { ), having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -3183,6 +3207,7 @@ Query { ), having: None, window_list: None, + qualify: None, }, ), order_by: [ @@ -3346,6 +3371,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -3391,6 +3417,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -3462,6 +3489,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -3507,6 +3535,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -3585,6 +3614,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -3630,6 +3660,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -3677,6 +3708,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -3755,6 +3787,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -3800,6 +3833,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -3847,6 +3881,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -3918,6 +3953,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: SetOperation( @@ -3970,6 +4006,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -4015,6 +4052,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -4095,6 +4133,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -4140,6 +4179,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -4187,6 +4227,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -4258,6 +4299,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: SetOperation( @@ -4310,6 +4352,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -4355,6 +4398,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -4439,6 +4483,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -4465,6 +4510,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -4492,6 +4538,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -4572,6 +4619,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), right: Select( @@ -4598,6 +4646,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), }, @@ -4625,6 +4674,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -4717,6 +4767,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [ @@ -4783,6 +4834,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -4923,6 +4975,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [ @@ -5053,6 +5106,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [ @@ -5151,6 +5205,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5308,6 +5363,7 @@ Query { }, ], ), + qualify: None, }, ), order_by: [], @@ -5634,6 +5690,7 @@ Query { }, ], ), + qualify: None, }, ), order_by: [ @@ -5747,6 +5804,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [ @@ -5813,6 +5871,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5957,6 +6016,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6171,6 +6231,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6217,6 +6278,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6231,6 +6293,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6348,6 +6411,7 @@ Query { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], diff --git a/src/query/ast/tests/it/testdata/statement-error.txt b/src/query/ast/tests/it/testdata/statement-error.txt index 8f0a78885764d..87f6bb7dd94e1 100644 --- a/src/query/ast/tests/it/testdata/statement-error.txt +++ b/src/query/ast/tests/it/testdata/statement-error.txt @@ -405,7 +405,7 @@ error: --> SQL:1:35 | 1 | SELECT * FROM t GROUP BY GROUPING SETS a, b - | ^^^^ unexpected `SETS`, expecting `SELECT`, `INTERSECT`, `WITH`, `EXCEPT`, `VALUES`, `OFFSET`, `IGNORE_RESULT`, `,`, `HAVING`, `WINDOW`, `(`, `UNION`, `FROM`, `ORDER`, `LIMIT`, `FORMAT`, or `;` + | ^^^^ unexpected `SETS`, expecting `SELECT`, `INTERSECT`, `WITH`, `EXCEPT`, `VALUES`, `OFFSET`, `IGNORE_RESULT`, `,`, `HAVING`, `WINDOW`, `QUALIFY`, `(`, `UNION`, `FROM`, `ORDER`, `LIMIT`, `FORMAT`, or `;` ---------- Input ---------- diff --git a/src/query/ast/tests/it/testdata/statement.txt b/src/query/ast/tests/it/testdata/statement.txt index e2de666599ad7..58c174f33b02d 100644 --- a/src/query/ast/tests/it/testdata/statement.txt +++ b/src/query/ast/tests/it/testdata/statement.txt @@ -556,6 +556,7 @@ Explain { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -632,6 +633,7 @@ Explain { group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -988,6 +990,7 @@ CreateTable( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -2047,6 +2050,7 @@ CreateView( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -2161,6 +2165,7 @@ AlterView( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -2305,6 +2310,7 @@ CreateView( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -2427,6 +2433,7 @@ AlterView( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -3066,6 +3073,7 @@ CreateTable( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -3497,6 +3505,7 @@ Query( }, ), window_list: None, + qualify: None, }, ), order_by: [], @@ -3561,6 +3570,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -3633,6 +3643,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -3733,6 +3744,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -3833,6 +3845,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [ @@ -4022,6 +4035,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -4168,6 +4182,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -4314,6 +4329,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -4460,6 +4476,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -4606,6 +4623,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -4752,6 +4770,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -4898,6 +4917,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5044,6 +5064,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5190,6 +5211,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5336,6 +5358,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5482,6 +5505,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5583,6 +5607,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5684,6 +5709,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5785,6 +5811,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -5886,6 +5913,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6048,6 +6076,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6061,6 +6090,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6223,6 +6253,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6236,6 +6267,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6398,6 +6430,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6411,6 +6444,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6571,6 +6605,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6584,6 +6619,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6693,6 +6729,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6803,6 +6840,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6868,6 +6906,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6914,6 +6953,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -6960,6 +7000,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -7006,6 +7047,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -7195,6 +7237,7 @@ Insert( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -7291,6 +7334,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -12132,6 +12176,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -12229,6 +12274,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -12381,6 +12427,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -12475,6 +12522,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -12655,6 +12703,7 @@ Query( ), having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -12794,6 +12843,7 @@ Query( ), having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -12949,6 +12999,7 @@ Query( ), having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -13087,6 +13138,7 @@ Query( ), having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -13204,6 +13256,7 @@ Query( ), having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -13321,6 +13374,7 @@ Query( ), having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -14333,6 +14387,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -14380,6 +14435,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], @@ -14427,6 +14483,7 @@ Query( group_by: None, having: None, window_list: None, + qualify: None, }, ), order_by: [], diff --git a/src/query/sql/src/planner/binder/bind_context.rs b/src/query/sql/src/planner/binder/bind_context.rs index a4ab5ba3a3e19..ee2fb64fcbac1 100644 --- a/src/query/sql/src/planner/binder/bind_context.rs +++ b/src/query/sql/src/planner/binder/bind_context.rs @@ -56,6 +56,7 @@ pub enum ExprContext { WhereClause, GroupClaue, HavingClause, + QualifyClause, OrderByClause, LimitClause, diff --git a/src/query/sql/src/planner/binder/copy_into_table.rs b/src/query/sql/src/planner/binder/copy_into_table.rs index 0cec396b2b71f..facffa70bc67c 100644 --- a/src/query/sql/src/planner/binder/copy_into_table.rs +++ b/src/query/sql/src/planner/binder/copy_into_table.rs @@ -331,8 +331,11 @@ impl<'a> Binder { let select_list = self .normalize_select_list(&mut from_context, select_list) .await?; - let (scalar_items, projections) = - self.analyze_projection(&from_context.aggregate_info, &select_list)?; + let (scalar_items, projections) = self.analyze_projection( + &from_context.aggregate_info, + &from_context.windows, + &select_list, + )?; if projections.len() != plan.required_source_schema.num_fields() { return Err(ErrorCode::BadArguments(format!( diff --git a/src/query/sql/src/planner/binder/mod.rs b/src/query/sql/src/planner/binder/mod.rs index 9b23c8c78bb7a..4ed733df8e857 100644 --- a/src/query/sql/src/planner/binder/mod.rs +++ b/src/query/sql/src/planner/binder/mod.rs @@ -36,6 +36,7 @@ mod merge_into; mod presign; mod project; mod project_set; +mod qualify; mod replace; mod scalar; mod scalar_common; diff --git a/src/query/sql/src/planner/binder/project.rs b/src/query/sql/src/planner/binder/project.rs index 7e10ff86fa1c9..58c4e16b6ed2e 100644 --- a/src/query/sql/src/planner/binder/project.rs +++ b/src/query/sql/src/planner/binder/project.rs @@ -40,6 +40,8 @@ use super::AggregateInfo; use crate::binder::aggregate::find_replaced_aggregate_function; use crate::binder::select::SelectItem; use crate::binder::select::SelectList; +use crate::binder::window::find_replaced_window_function; +use crate::binder::window::WindowInfo; use crate::binder::ExprContext; use crate::binder::Visibility; use crate::optimizer::SExpr; @@ -73,6 +75,7 @@ impl Binder { pub fn analyze_projection( &mut self, agg_info: &AggregateInfo, + window_info: &WindowInfo, select_list: &SelectList, ) -> Result<(HashMap, Vec)> { let mut columns = Vec::with_capacity(select_list.items.len()); @@ -94,6 +97,9 @@ impl Binder { debug_assert!(!is_grouping_sets_item); find_replaced_aggregate_function(agg_info, agg, &item.alias).unwrap() } + ScalarExpr::WindowFunction(win) => { + find_replaced_window_function(window_info, win, &item.alias).unwrap() + } _ => { self.create_derived_column_binding(item.alias.clone(), item.scalar.data_type()?) } diff --git a/src/query/sql/src/planner/binder/qualify.rs b/src/query/sql/src/planner/binder/qualify.rs new file mode 100644 index 0000000000000..56977cc14f7a5 --- /dev/null +++ b/src/query/sql/src/planner/binder/qualify.rs @@ -0,0 +1,184 @@ +// Copyright 2021 Datafuse Labs +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::Arc; + +use common_ast::ast::Expr; +use common_exception::ErrorCode; +use common_exception::Result; + +use super::Finder; +use crate::binder::split_conjunctions; +use crate::binder::window::WindowRewriter; +use crate::binder::ColumnBindingBuilder; +use crate::binder::ExprContext; +use crate::binder::ScalarBinder; +use crate::binder::Visibility; +use crate::optimizer::SExpr; +use crate::planner::semantic::GroupingChecker; +use crate::plans::walk_expr_mut; +use crate::plans::BoundColumnRef; +use crate::plans::Filter; +use crate::plans::ScalarExpr; +use crate::plans::SubqueryExpr; +use crate::plans::Visitor; +use crate::plans::VisitorMut; +use crate::BindContext; +use crate::Binder; + +impl Binder { + /// Analyze window in qualify clause, this will rewrite window functions. + /// See `WindowRewriter` for more details. + #[async_backtrace::framed] + pub async fn analyze_window_qualify<'a>( + &mut self, + bind_context: &mut BindContext, + aliases: &[(String, ScalarExpr)], + qualify: &Expr, + ) -> Result { + bind_context.set_expr_context(ExprContext::QualifyClause); + let mut scalar_binder = ScalarBinder::new( + bind_context, + self.ctx.clone(), + &self.name_resolution_ctx, + self.metadata.clone(), + aliases, + self.m_cte_bound_ctx.clone(), + self.ctes_map.clone(), + ); + let (mut scalar, _) = scalar_binder.bind(qualify).await?; + let mut rewriter = WindowRewriter::new(bind_context, self.metadata.clone()); + rewriter.visit(&mut scalar)?; + Ok(scalar) + } + + #[async_backtrace::framed] + pub async fn bind_qualify( + &mut self, + bind_context: &mut BindContext, + qualify: ScalarExpr, + child: SExpr, + ) -> Result { + bind_context.set_expr_context(ExprContext::QualifyClause); + + let f = |scalar: &ScalarExpr| matches!(scalar, ScalarExpr::AggregateFunction(_)); + let mut finder = Finder::new(&f); + finder.visit(&qualify)?; + if !finder.scalars().is_empty() { + return Err(ErrorCode::SemanticError( + "Qualify clause must not contain aggregate functions".to_string(), + ) + .set_span(qualify.span())); + } + + let scalar = { + let mut qualify = qualify; + if bind_context.in_grouping { + // If we are in grouping context, we will perform the grouping check + let mut grouping_checker = GroupingChecker::new(bind_context); + grouping_checker.visit(&mut qualify)?; + } else { + let mut qualify_checker = QualifyChecker::new(bind_context); + qualify_checker.visit(&mut qualify)?; + } + qualify + }; + + let predicates = split_conjunctions(&scalar); + + let filter = Filter { predicates }; + + Ok(SExpr::create_unary( + Arc::new(filter.into()), + Arc::new(child), + )) + } +} + +pub struct QualifyChecker<'a> { + bind_context: &'a BindContext, +} + +impl<'a> QualifyChecker<'a> { + pub fn new(bind_context: &'a BindContext) -> Self { + Self { bind_context } + } +} + +impl<'a> VisitorMut<'_> for QualifyChecker<'a> { + fn visit(&mut self, expr: &mut ScalarExpr) -> Result<()> { + if let ScalarExpr::WindowFunction(window) = expr { + if let Some(column) = self + .bind_context + .windows + .window_functions_map + .get(&window.display_name) + { + // The exprs in `win` has already been rewrittern to `BoundColumnRef` in `WindowRewriter`. + // So we need to check the exprs in `bind_context.windows` + let window_info = &self.bind_context.windows.window_functions[*column]; + + let column_binding = ColumnBindingBuilder::new( + window.display_name.clone(), + window_info.index, + Box::new(window_info.func.return_type()), + Visibility::Visible, + ) + .build(); + *expr = BoundColumnRef { + span: None, + column: column_binding, + } + .into(); + return Ok(()); + } + return Err(ErrorCode::Internal( + "Qualify check: Invalid window function", + )); + } + + if let ScalarExpr::AggregateFunction(agg) = expr { + if let Some(column) = self + .bind_context + .aggregate_info + .aggregate_functions_map + .get(&agg.display_name) + { + let agg_func = &self.bind_context.aggregate_info.aggregate_functions[*column]; + let column_binding = ColumnBindingBuilder::new( + agg.display_name.clone(), + agg_func.index, + Box::new(agg_func.scalar.data_type()?), + Visibility::Visible, + ) + .build(); + *expr = BoundColumnRef { + span: None, + column: column_binding, + } + .into(); + return Ok(()); + } + + return Err(ErrorCode::Internal("Invalid aggregate function")); + } + + walk_expr_mut(self, expr) + } + + fn visit_subquery_expr(&mut self, _: &mut SubqueryExpr) -> Result<()> { + // TODO(leiysky): check subquery in the future + Ok(()) + } +} diff --git a/src/query/sql/src/planner/binder/select.rs b/src/query/sql/src/planner/binder/select.rs index ad6681c7552d8..4d2fef7057150 100644 --- a/src/query/sql/src/planner/binder/select.rs +++ b/src/query/sql/src/planner/binder/select.rs @@ -200,8 +200,11 @@ impl Binder { }; // `analyze_projection` should behind `analyze_aggregate_select` because `analyze_aggregate_select` will rewrite `grouping`. - let (mut scalar_items, projections) = - self.analyze_projection(&from_context.aggregate_info, &select_list)?; + let (mut scalar_items, projections) = self.analyze_projection( + &from_context.aggregate_info, + &from_context.windows, + &select_list, + )?; let having = if let Some(having) = &stmt.having { Some( @@ -212,6 +215,15 @@ impl Binder { None }; + let qualify = if let Some(qualify) = &stmt.qualify { + Some( + self.analyze_window_qualify(&mut from_context, &aliases, qualify) + .await?, + ) + } else { + None + }; + let order_items = self .analyze_order_items( &mut from_context, @@ -253,6 +265,12 @@ impl Binder { s_expr = self.bind_window_function(window_info, s_expr).await?; } + if let Some(qualify) = qualify { + s_expr = self + .bind_qualify(&mut from_context, qualify, s_expr) + .await?; + } + if stmt.distinct { s_expr = self.bind_distinct( stmt.span, diff --git a/src/query/sql/src/planner/binder/table.rs b/src/query/sql/src/planner/binder/table.rs index 3b066caea5e3a..8887916f4508e 100644 --- a/src/query/sql/src/planner/binder/table.rs +++ b/src/query/sql/src/planner/binder/table.rs @@ -566,6 +566,7 @@ impl Binder { group_by: None, having: None, window_list: None, + qualify: None, }; let (srf_expr, mut bind_context) = self .bind_select_stmt(bind_context, &select_stmt, &[], 0) diff --git a/src/query/sql/src/planner/binder/window.rs b/src/query/sql/src/planner/binder/window.rs index b05b709828575..088596c5d4d89 100644 --- a/src/query/sql/src/planner/binder/window.rs +++ b/src/query/sql/src/planner/binder/window.rs @@ -22,6 +22,7 @@ use common_exception::Result; use common_exception::Span; use super::select::SelectList; +use crate::binder::ColumnBinding; use crate::binder::ColumnBindingBuilder; use crate::optimizer::SExpr; use crate::plans::walk_expr_mut; @@ -344,8 +345,8 @@ impl<'a> WindowRewriter<'a> { frame: window.frame.clone(), }; - let window_infos = &mut self.bind_context.windows; // push window info to BindContext + let window_infos = &mut self.bind_context.windows; window_infos.window_functions.push(window_info); window_infos.window_functions_map.insert( window.display_name.clone(), @@ -423,7 +424,7 @@ impl<'a> WindowRewriter<'a> { } } - fn as_window_aggregate_rewriter(&self) -> WindowAggregateRewriter { + pub fn as_window_aggregate_rewriter(&self) -> WindowAggregateRewriter { WindowAggregateRewriter { bind_context: self.bind_context, } @@ -431,6 +432,23 @@ impl<'a> WindowRewriter<'a> { } impl<'a> VisitorMut<'a> for WindowRewriter<'a> { + fn visit(&mut self, expr: &'a mut ScalarExpr) -> Result<()> { + if let ScalarExpr::WindowFunction(window) = expr { + *expr = { + let window_infos = &self.bind_context.windows; + + if let Some(column) = + find_replaced_window_function(window_infos, window, &window.display_name) + { + BoundColumnRef { span: None, column }.into() + } else { + self.replace_window_function(window)?.into() + } + }; + return Ok(()); + } + walk_expr_mut(self, expr) + } fn visit_window_function(&mut self, window: &'a mut WindowFunc) -> Result<()> { *window = self.replace_window_function(window)?; Ok(()) @@ -480,6 +498,31 @@ impl<'a> VisitorMut<'a> for WindowAggregateRewriter<'a> { } } +/// Replace [`WindowFunction`] with a [`ColumnBinding`] if the function is already replaced. +pub fn find_replaced_window_function( + window_info: &WindowInfo, + window: &WindowFunc, + new_name: &str, +) -> Option { + window_info + .window_functions_map + .get(&window.display_name) + .map(|i| { + let window_func_info = &window_info.window_functions[*i]; + debug_assert_eq!( + window_func_info.func.return_type(), + window.func.return_type() + ); + ColumnBindingBuilder::new( + new_name.to_string(), + window_func_info.index, + Box::new(window.func.return_type()), + Visibility::Visible, + ) + .build() + }) +} + impl Binder { /// Analyze windows in select clause, this will rewrite window functions. /// See [`WindowRewriter`] for more details. diff --git a/src/query/sql/src/planner/dataframe.rs b/src/query/sql/src/planner/dataframe.rs index 38a7837a72fd6..d9703625d7dd9 100644 --- a/src/query/sql/src/planner/dataframe.rs +++ b/src/query/sql/src/planner/dataframe.rs @@ -144,9 +144,11 @@ impl Dataframe { .normalize_select_list(bind_context, select_list) .await?; - let (scalar_items, projections) = self - .binder - .analyze_projection(&bind_context.aggregate_info, &select_list)?; + let (scalar_items, projections) = self.binder.analyze_projection( + &bind_context.aggregate_info, + &bind_context.windows, + &select_list, + )?; self.s_expr = self.binder.bind_projection( &mut self.bind_context, @@ -192,9 +194,11 @@ impl Dataframe { self.binder .analyze_aggregate_select(&mut self.bind_context, &mut select_list)?; - let (scalar_items, projections) = self - .binder - .analyze_projection(&self.bind_context.aggregate_info, &select_list)?; + let (scalar_items, projections) = self.binder.analyze_projection( + &self.bind_context.aggregate_info, + &self.bind_context.windows, + &select_list, + )?; self.s_expr = self .binder @@ -266,9 +270,11 @@ impl Dataframe { .await?; } - let (scalar_items, projections) = self - .binder - .analyze_projection(&self.bind_context.aggregate_info, &select_list)?; + let (scalar_items, projections) = self.binder.analyze_projection( + &self.bind_context.aggregate_info, + &self.bind_context.windows, + &select_list, + )?; self.s_expr = self.binder.bind_projection( &mut self.bind_context, @@ -305,9 +311,11 @@ impl Dataframe { .await?; self.binder .analyze_aggregate_select(&mut self.bind_context, &mut select_list)?; - let (mut scalar_items, projections) = self - .binder - .analyze_projection(&self.bind_context.aggregate_info, &select_list)?; + let (mut scalar_items, projections) = self.binder.analyze_projection( + &self.bind_context.aggregate_info, + &self.bind_context.windows, + &select_list, + )?; self.s_expr = self.binder.bind_distinct( None, &self.bind_context, @@ -387,9 +395,11 @@ impl Dataframe { .iter() .map(|item| (item.alias.clone(), item.scalar.clone())) .collect::>(); - let (mut scalar_items, projections) = self - .binder - .analyze_projection(&self.bind_context.aggregate_info, &select_list)?; + let (mut scalar_items, projections) = self.binder.analyze_projection( + &self.bind_context.aggregate_info, + &self.bind_context.windows, + &select_list, + )?; let order_items = self .binder .analyze_order_items( diff --git a/src/query/sql/src/planner/semantic/aggregating_index_visitor.rs b/src/query/sql/src/planner/semantic/aggregating_index_visitor.rs index c8e18047cf2c3..300560df7cd3d 100644 --- a/src/query/sql/src/planner/semantic/aggregating_index_visitor.rs +++ b/src/query/sql/src/planner/semantic/aggregating_index_visitor.rs @@ -197,7 +197,7 @@ impl<'ast> Visitor<'ast> for AggregatingIndexChecker { if self.not_support { return; } - if stmt.having.is_some() || stmt.window_list.is_some() { + if stmt.having.is_some() || stmt.window_list.is_some() || stmt.qualify.is_some() { self.not_support = true; return; } diff --git a/src/query/sql/src/planner/semantic/distinct_to_groupby.rs b/src/query/sql/src/planner/semantic/distinct_to_groupby.rs index f3c04152fb962..40ec105c67eae 100644 --- a/src/query/sql/src/planner/semantic/distinct_to_groupby.rs +++ b/src/query/sql/src/planner/semantic/distinct_to_groupby.rs @@ -36,6 +36,7 @@ impl VisitorMut for DistinctToGroupBy { group_by, having, window_list, + qualify, .. } = stmt; @@ -76,6 +77,7 @@ impl VisitorMut for DistinctToGroupBy { group_by: Some(GroupBy::Normal(args.clone())), having: None, window_list: None, + qualify: None, })), order_by: vec![], limit: vec![], @@ -121,6 +123,7 @@ impl VisitorMut for DistinctToGroupBy { group_by: None, having: having.clone(), window_list: window_list.clone(), + qualify: qualify.clone(), }; *stmt = new_stmt; diff --git a/src/tests/sqlsmith/src/sql_gen/query.rs b/src/tests/sqlsmith/src/sql_gen/query.rs index 042a9f44b7b94..68419545d2177 100644 --- a/src/tests/sqlsmith/src/sql_gen/query.rs +++ b/src/tests/sqlsmith/src/sql_gen/query.rs @@ -120,6 +120,7 @@ impl<'a, R: Rng> SqlGenerator<'a, R> { group_by: None, having: None, window_list: None, + qualify: None, }; let body = SetExpr::Select(Box::new(select)); @@ -311,6 +312,7 @@ impl<'a, R: Rng> SqlGenerator<'a, R> { group_by, having: self.gen_selection(), window_list: self.gen_window_list(), + qualify: None, // todo: add qualify. } } diff --git a/tests/sqllogictests/suites/mode/standalone/explain/window.test b/tests/sqllogictests/suites/mode/standalone/explain/window.test index 5d7fe30e5c766..14770c0dec514 100644 --- a/tests/sqllogictests/suites/mode/standalone/explain/window.test +++ b/tests/sqllogictests/suites/mode/standalone/explain/window.test @@ -13,29 +13,25 @@ CREATE TABLE empsalary (depname string, empno bigint, salary int, enroll_date da query explain SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname ORDER BY empno) FROM empsalary ORDER BY depname, empno ---- -EvalScalar -├── output columns: [empsalary.depname (#0), empsalary.empno (#1), empsalary.salary (#2), sum(salary) over (partition by depname order by empno) (#5)] -├── expressions: [sum(salary) OVER (PARTITION BY depname ORDER BY empno) (#4)] +Sort +├── output columns: [empsalary.depname (#0), empsalary.empno (#1), empsalary.salary (#2), sum(salary) OVER (PARTITION BY depname ORDER BY empno) (#4)] +├── sort keys: [depname ASC NULLS LAST, empno ASC NULLS LAST] ├── estimated rows: 0.00 -└── Sort +└── Window ├── output columns: [empsalary.depname (#0), empsalary.empno (#1), empsalary.salary (#2), sum(salary) OVER (PARTITION BY depname ORDER BY empno) (#4)] - ├── sort keys: [depname ASC NULLS LAST, empno ASC NULLS LAST] - ├── estimated rows: 0.00 - └── Window - ├── output columns: [empsalary.depname (#0), empsalary.empno (#1), empsalary.salary (#2), sum(salary) OVER (PARTITION BY depname ORDER BY empno) (#4)] - ├── aggregate function: [sum(salary)] - ├── partition by: [depname] - ├── order by: [empno] - ├── frame: [Range: Preceding(None) ~ CurrentRow] - └── TableScan - ├── table: default.test_explain_window.empsalary - ├── output columns: [depname (#0), empno (#1), salary (#2)] - ├── read rows: 0 - ├── read bytes: 0 - ├── partitions total: 0 - ├── partitions scanned: 0 - ├── push downs: [filters: [], limit: NONE] - └── estimated rows: 0.00 + ├── aggregate function: [sum(salary)] + ├── partition by: [depname] + ├── order by: [empno] + ├── frame: [Range: Preceding(None) ~ CurrentRow] + └── TableScan + ├── table: default.test_explain_window.empsalary + ├── output columns: [depname (#0), empno (#1), salary (#2)] + ├── read rows: 0 + ├── read bytes: 0 + ├── partitions total: 0 + ├── partitions scanned: 0 + ├── push downs: [filters: [], limit: NONE] + └── estimated rows: 0.00 statement ok set max_threads=4; @@ -44,15 +40,14 @@ query explain pipeline SELECT depname, empno, salary, sum(salary) OVER (PARTITION BY depname ORDER BY empno) FROM empsalary ORDER BY depname, empno; ---- CompoundBlockOperator(Project) × 1 processor - CompoundBlockOperator(Map) × 1 processor - Merge (SortMergeTransform × 4 processors) to (CompoundBlockOperator(Map) × 1) - SortMergeTransform × 4 processors - SortPartialTransform × 4 processors - Merge (Transform Window × 1 processor) to (SortPartialTransform × 4) - Transform Window × 1 processor - Merge (SortMergeTransform × 4 processors) to (Transform Window × 1) - SortMergeTransform × 4 processors - SortPartialTransform × 4 processors - Merge (DeserializeDataTransform × 1 processor) to (SortPartialTransform × 4) - DeserializeDataTransform × 1 processor - SyncReadParquetDataSource × 1 processor + Merge (SortMergeTransform × 4 processors) to (CompoundBlockOperator(Project) × 1) + SortMergeTransform × 4 processors + SortPartialTransform × 4 processors + Merge (Transform Window × 1 processor) to (SortPartialTransform × 4) + Transform Window × 1 processor + Merge (SortMergeTransform × 4 processors) to (Transform Window × 1) + SortMergeTransform × 4 processors + SortPartialTransform × 4 processors + Merge (DeserializeDataTransform × 1 processor) to (SortPartialTransform × 4) + DeserializeDataTransform × 1 processor + SyncReadParquetDataSource × 1 processor diff --git a/tests/sqllogictests/suites/query/window_function/window_qualify.test b/tests/sqllogictests/suites/query/window_function/window_qualify.test new file mode 100644 index 0000000000000..f4336c690d3b3 --- /dev/null +++ b/tests/sqllogictests/suites/query/window_function/window_qualify.test @@ -0,0 +1,64 @@ +statement ok +CREATE DATABASE IF NOT EXISTS test_window_qualify + +statement ok +USE test_window_qualify + +statement ok +DROP TABLE IF EXISTS qt + +statement ok +CREATE TABLE qt (i INTEGER, p CHAR(1), o INTEGER) + +statement ok +INSERT INTO qt (i, p, o) VALUES (1, 'A', 1), (2, 'A', 2), (3, 'B', 1), (4, 'B', 2) + +# window function in qualify clause +query ITI +SELECT i, p, o FROM qt QUALIFY ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) = 1 +---- +1 A 1 +3 B 1 + +# named window, other same as above +query ITI +SELECT i, p, o FROM qt WINDOW w AS (PARTITION BY p ORDER BY o) QUALIFY ROW_NUMBER() OVER w = 1 +---- +1 A 1 +3 B 1 + +# window function in select list +query ITII +SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) AS rn FROM qt QUALIFY rn = 1 +---- +1 A 1 1 +3 B 1 1 + +# named window, other same as above +query ITII +SELECT i, p, o, ROW_NUMBER() OVER w AS rn FROM qt WINDOW w AS (PARTITION BY p ORDER BY o) QUALIFY rn = 1 +---- +1 A 1 1 +3 B 1 1 + +# qualify with subquery +query ITII +SELECT i, p, o, ROW_NUMBER() OVER w AS rn FROM qt WINDOW w AS (PARTITION BY p ORDER BY o) QUALIFY rn = (SELECT i FROM qt LIMIT 1) +---- +1 A 1 1 +3 B 1 1 + +# without qualify +query ITII +SELECT i, p, o, ROW_NUMBER() OVER (PARTITION BY p ORDER BY o) AS row_num FROM qt +---- +1 A 1 1 +2 A 2 2 +3 B 1 1 +4 B 2 2 + +statement ok +USE default + +statement ok +DROP DATABASE test_window_qualify \ No newline at end of file