Accept WINDOW clause after QUALIFY when parsing (#1248)

Co-authored-by: ifeanyi <ifeanyi@validio.io>
This commit is contained in:
Andrew Lamb 2024-05-03 14:02:09 -04:00 committed by GitHub
parent 71a7262e38
commit 8e64b73e9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 86 additions and 14 deletions

View file

@ -245,6 +245,11 @@ pub struct Select {
pub named_window: Vec<NamedWindowDefinition>,
/// QUALIFY (Snowflake)
pub qualify: Option<Expr>,
/// The positioning of QUALIFY and WINDOW clauses differ between dialects.
/// e.g. BigQuery requires that WINDOW comes after QUALIFY, while DUCKDB accepts
/// WINDOW before QUALIFY.
/// We accept either positioning and flag the accepted variant.
pub window_before_qualify: bool,
/// BigQuery syntax: `SELECT AS VALUE | SELECT AS STRUCT`
pub value_table_mode: Option<ValueTableMode>,
/// STARTING WITH .. CONNECT BY
@ -310,11 +315,20 @@ impl fmt::Display for Select {
if let Some(ref having) = self.having {
write!(f, " HAVING {having}")?;
}
if !self.named_window.is_empty() {
write!(f, " WINDOW {}", display_comma_separated(&self.named_window))?;
}
if let Some(ref qualify) = self.qualify {
write!(f, " QUALIFY {qualify}")?;
if self.window_before_qualify {
if !self.named_window.is_empty() {
write!(f, " WINDOW {}", display_comma_separated(&self.named_window))?;
}
if let Some(ref qualify) = self.qualify {
write!(f, " QUALIFY {qualify}")?;
}
} else {
if let Some(ref qualify) = self.qualify {
write!(f, " QUALIFY {qualify}")?;
}
if !self.named_window.is_empty() {
write!(f, " WINDOW {}", display_comma_separated(&self.named_window))?;
}
}
if let Some(ref connect_by) = self.connect_by {
write!(f, " {connect_by}")?;

View file

@ -7854,16 +7854,28 @@ impl<'a> Parser<'a> {
None
};
let named_windows = if self.parse_keyword(Keyword::WINDOW) {
self.parse_comma_separated(Parser::parse_named_window)?
// Accept QUALIFY and WINDOW in any order and flag accordingly.
let (named_windows, qualify, window_before_qualify) = if self.parse_keyword(Keyword::WINDOW)
{
let named_windows = self.parse_comma_separated(Parser::parse_named_window)?;
if self.parse_keyword(Keyword::QUALIFY) {
(named_windows, Some(self.parse_expr()?), true)
} else {
(named_windows, None, true)
}
} else if self.parse_keyword(Keyword::QUALIFY) {
let qualify = Some(self.parse_expr()?);
if self.parse_keyword(Keyword::WINDOW) {
(
self.parse_comma_separated(Parser::parse_named_window)?,
qualify,
false,
)
} else {
(Default::default(), qualify, false)
}
} else {
vec![]
};
let qualify = if self.parse_keyword(Keyword::QUALIFY) {
Some(self.parse_expr()?)
} else {
None
Default::default()
};
let connect_by = if self.dialect.supports_connect_by()
@ -7891,6 +7903,7 @@ impl<'a> Parser<'a> {
sort_by,
having,
named_window: named_windows,
window_before_qualify,
qualify,
value_table_mode,
connect_by,

View file

@ -114,6 +114,7 @@ fn parse_map_access_expr() {
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
connect_by: None,

View file

@ -401,6 +401,7 @@ fn parse_update_set_from() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),
@ -4546,12 +4547,34 @@ fn test_parse_named_window() {
),
],
qualify: None,
window_before_qualify: true,
value_table_mode: None,
connect_by: None,
};
assert_eq!(actual_select_only, expected);
}
#[test]
fn parse_window_and_qualify_clause() {
let sql = "SELECT \
MIN(c12) OVER window1 AS min1 \
FROM aggregate_test_100 \
QUALIFY ROW_NUMBER() OVER my_window \
WINDOW window1 AS (ORDER BY C12), \
window2 AS (PARTITION BY C11) \
ORDER BY C3";
verified_only_select(sql);
let sql = "SELECT \
MIN(c12) OVER window1 AS min1 \
FROM aggregate_test_100 \
WINDOW window1 AS (ORDER BY C12), \
window2 AS (PARTITION BY C11) \
QUALIFY ROW_NUMBER() OVER my_window \
ORDER BY C3";
verified_only_select(sql);
}
#[test]
fn parse_window_clause_named_window() {
let sql = "SELECT * FROM mytable WINDOW window1 AS window2";
@ -4918,6 +4941,7 @@ fn parse_interval_and_or_xor() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),
@ -6872,6 +6896,7 @@ fn lateral_function() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
};
@ -7516,6 +7541,7 @@ fn parse_merge() {
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
connect_by: None,
@ -8958,6 +8984,7 @@ fn parse_unload() {
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
connect_by: None,
@ -9112,6 +9139,7 @@ fn parse_connect_by() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: Some(ConnectBy {
condition: Expr::BinaryOp {
@ -9199,6 +9227,7 @@ fn parse_connect_by() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: Some(ConnectBy {
condition: Expr::BinaryOp {

View file

@ -177,6 +177,7 @@ fn test_select_union_by_name() {
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
connect_by: None,
@ -214,6 +215,7 @@ fn test_select_union_by_name() {
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
connect_by: None,

View file

@ -117,6 +117,7 @@ fn parse_create_procedure() {
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
connect_by: None,
@ -525,6 +526,7 @@ fn parse_substring_in_select() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),

View file

@ -908,6 +908,7 @@ fn parse_escaped_quote_identifiers_with_escape() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),
@ -954,6 +955,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),
@ -997,6 +999,7 @@ fn parse_escaped_backticks_with_escape() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),
@ -1040,6 +1043,7 @@ fn parse_escaped_backticks_with_no_escape() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),
@ -1745,6 +1749,7 @@ fn parse_select_with_numeric_prefix_column_name() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
})))
@ -1797,6 +1802,7 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
})))
@ -2291,6 +2297,7 @@ fn parse_substring_in_select() {
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
connect_by: None,
@ -2603,6 +2610,7 @@ fn parse_hex_string_introducer() {
sort_by: vec![],
having: None,
named_window: vec![],
window_before_qualify: false,
qualify: None,
value_table_mode: None,
into: None,

View file

@ -1078,6 +1078,7 @@ fn parse_copy_to() {
group_by: GroupByExpr::Expressions(vec![]),
having: None,
named_window: vec![],
window_before_qualify: false,
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
@ -2170,6 +2171,7 @@ fn parse_array_subquery_expr() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),
@ -2188,6 +2190,7 @@ fn parse_array_subquery_expr() {
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),