Skip to content

Commit e7af75c

Browse files
yuval-illumexalamb
authored andcommitted
Support IGNORE|RESPECT NULLs clause in window functions (apache#998)
Co-authored-by: Andrew Lamb <[email protected]>
1 parent b6efd14 commit e7af75c

14 files changed

+124
-0
lines changed

src/ast/mod.rs

+26
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,26 @@ impl fmt::Display for WindowFrameUnits {
11611161
}
11621162
}
11631163

1164+
/// Specifies Ignore / Respect NULL within window functions.
1165+
/// For example
1166+
/// `FIRST_VALUE(column2) IGNORE NULLS OVER (PARTITION BY column1)`
1167+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1168+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1169+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1170+
pub enum NullTreatment {
1171+
IgnoreNulls,
1172+
RespectNulls,
1173+
}
1174+
1175+
impl fmt::Display for NullTreatment {
1176+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1177+
f.write_str(match self {
1178+
NullTreatment::IgnoreNulls => "IGNORE NULLS",
1179+
NullTreatment::RespectNulls => "RESPECT NULLS",
1180+
})
1181+
}
1182+
}
1183+
11641184
/// Specifies [WindowFrame]'s `start_bound` and `end_bound`
11651185
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
11661186
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -4036,6 +4056,8 @@ pub struct Function {
40364056
pub args: Vec<FunctionArg>,
40374057
/// e.g. `x > 5` in `COUNT(x) FILTER (WHERE x > 5)`
40384058
pub filter: Option<Box<Expr>>,
4059+
// Snowflake/MSSQL supports diffrent options for null treatment in rank functions
4060+
pub null_treatment: Option<NullTreatment>,
40394061
pub over: Option<WindowType>,
40404062
// aggregate functions may specify eg `COUNT(DISTINCT x)`
40414063
pub distinct: bool,
@@ -4088,6 +4110,10 @@ impl fmt::Display for Function {
40884110
write!(f, " FILTER (WHERE {filter_cond})")?;
40894111
}
40904112

4113+
if let Some(o) = &self.null_treatment {
4114+
write!(f, " {o}")?;
4115+
}
4116+
40914117
if let Some(o) = &self.over {
40924118
write!(f, " OVER {o}")?;
40934119
}

src/ast/visitor.rs

+1
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,7 @@ where
616616
/// *expr = Expr::Function(Function {
617617
/// name: ObjectName(vec![Ident::new("f")]),
618618
/// args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(old_expr))],
619+
/// null_treatment: None,
619620
/// filter: None, over: None, distinct: false, special: false, order_by: vec![],
620621
/// });
621622
/// }

src/keywords.rs

+1
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,7 @@ define_keywords!(
527527
REPLACE,
528528
REPLICATION,
529529
RESET,
530+
RESPECT,
530531
RESTRICT,
531532
RESULT,
532533
RETAIN,

src/parser/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,7 @@ impl<'a> Parser<'a> {
789789
Ok(Expr::Function(Function {
790790
name: ObjectName(vec![w.to_ident()]),
791791
args: vec![],
792+
null_treatment: None,
792793
filter: None,
793794
over: None,
794795
distinct: false,
@@ -991,6 +992,19 @@ impl<'a> Parser<'a> {
991992
} else {
992993
None
993994
};
995+
let null_treatment = match self.parse_one_of_keywords(&[Keyword::RESPECT, Keyword::IGNORE])
996+
{
997+
Some(keyword) => {
998+
self.expect_keyword(Keyword::NULLS)?;
999+
1000+
match keyword {
1001+
Keyword::RESPECT => Some(NullTreatment::RespectNulls),
1002+
Keyword::IGNORE => Some(NullTreatment::IgnoreNulls),
1003+
_ => None,
1004+
}
1005+
}
1006+
None => None,
1007+
};
9941008
let over = if self.parse_keyword(Keyword::OVER) {
9951009
if self.consume_token(&Token::LParen) {
9961010
let window_spec = self.parse_window_spec()?;
@@ -1004,6 +1018,7 @@ impl<'a> Parser<'a> {
10041018
Ok(Expr::Function(Function {
10051019
name,
10061020
args,
1021+
null_treatment,
10071022
filter,
10081023
over,
10091024
distinct,
@@ -1022,6 +1037,7 @@ impl<'a> Parser<'a> {
10221037
Ok(Expr::Function(Function {
10231038
name,
10241039
args,
1040+
null_treatment: None,
10251041
filter: None,
10261042
over: None,
10271043
distinct: false,

tests/sqlparser_bigquery.rs

+1
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,7 @@ fn parse_map_access_offset() {
564564
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
565565
number("0")
566566
))),],
567+
null_treatment: None,
567568
filter: None,
568569
over: None,
569570
distinct: false,

tests/sqlparser_clickhouse.rs

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ fn parse_map_access_expr() {
5050
Value::SingleQuotedString("endpoint".to_string())
5151
))),
5252
],
53+
null_treatment: None,
5354
filter: None,
5455
over: None,
5556
distinct: false,
@@ -90,6 +91,7 @@ fn parse_map_access_expr() {
9091
Value::SingleQuotedString("app".to_string())
9192
))),
9293
],
94+
null_treatment: None,
9395
filter: None,
9496
over: None,
9597
distinct: false,
@@ -140,6 +142,7 @@ fn parse_array_fn() {
140142
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(Ident::new("x1")))),
141143
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(Ident::new("x2")))),
142144
],
145+
null_treatment: None,
143146
filter: None,
144147
over: None,
145148
distinct: false,
@@ -199,6 +202,7 @@ fn parse_delimited_identifiers() {
199202
&Expr::Function(Function {
200203
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
201204
args: vec![],
205+
null_treatment: None,
202206
filter: None,
203207
over: None,
204208
distinct: false,

tests/sqlparser_common.rs

+58
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,7 @@ fn parse_select_count_wildcard() {
875875
&Expr::Function(Function {
876876
name: ObjectName(vec![Ident::new("COUNT")]),
877877
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)],
878+
null_treatment: None,
878879
filter: None,
879880
over: None,
880881
distinct: false,
@@ -896,6 +897,7 @@ fn parse_select_count_distinct() {
896897
op: UnaryOperator::Plus,
897898
expr: Box::new(Expr::Identifier(Ident::new("x"))),
898899
}))],
900+
null_treatment: None,
899901
filter: None,
900902
over: None,
901903
distinct: true,
@@ -1864,6 +1866,7 @@ fn parse_select_having() {
18641866
left: Box::new(Expr::Function(Function {
18651867
name: ObjectName(vec![Ident::new("COUNT")]),
18661868
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Wildcard)],
1869+
null_treatment: None,
18671870
filter: None,
18681871
over: None,
18691872
distinct: false,
@@ -1890,6 +1893,7 @@ fn parse_select_qualify() {
18901893
left: Box::new(Expr::Function(Function {
18911894
name: ObjectName(vec![Ident::new("ROW_NUMBER")]),
18921895
args: vec![],
1896+
null_treatment: None,
18931897
filter: None,
18941898
over: Some(WindowType::WindowSpec(WindowSpec {
18951899
partition_by: vec![Expr::Identifier(Ident::new("p"))],
@@ -2287,6 +2291,45 @@ fn parse_agg_with_order_by() {
22872291
}
22882292
}
22892293

2294+
#[test]
2295+
fn parse_window_rank_function() {
2296+
let supported_dialects = TestedDialects {
2297+
dialects: vec![
2298+
Box::new(GenericDialect {}),
2299+
Box::new(PostgreSqlDialect {}),
2300+
Box::new(MsSqlDialect {}),
2301+
Box::new(AnsiDialect {}),
2302+
Box::new(HiveDialect {}),
2303+
Box::new(SnowflakeDialect {}),
2304+
],
2305+
options: None,
2306+
};
2307+
2308+
for sql in [
2309+
"SELECT column1, column2, FIRST_VALUE(column2) OVER (PARTITION BY column1 ORDER BY column2 NULLS LAST) AS column2_first FROM t1",
2310+
"SELECT column1, column2, FIRST_VALUE(column2) OVER (ORDER BY column2 NULLS LAST) AS column2_first FROM t1",
2311+
"SELECT col_1, col_2, LAG(col_2) OVER (ORDER BY col_1) FROM t1",
2312+
"SELECT LAG(col_2, 1, 0) OVER (ORDER BY col_1) FROM t1",
2313+
"SELECT LAG(col_2, 1, 0) OVER (PARTITION BY col_3 ORDER BY col_1)",
2314+
] {
2315+
supported_dialects.verified_stmt(sql);
2316+
}
2317+
2318+
let supported_dialects_nulls = TestedDialects {
2319+
dialects: vec![Box::new(MsSqlDialect {}), Box::new(SnowflakeDialect {})],
2320+
options: None,
2321+
};
2322+
2323+
for sql in [
2324+
"SELECT column1, column2, FIRST_VALUE(column2) IGNORE NULLS OVER (PARTITION BY column1 ORDER BY column2 NULLS LAST) AS column2_first FROM t1",
2325+
"SELECT column1, column2, FIRST_VALUE(column2) RESPECT NULLS OVER (PARTITION BY column1 ORDER BY column2 NULLS LAST) AS column2_first FROM t1",
2326+
"SELECT LAG(col_2, 1, 0) IGNORE NULLS OVER (ORDER BY col_1) FROM t1",
2327+
"SELECT LAG(col_2, 1, 0) RESPECT NULLS OVER (ORDER BY col_1) FROM t1",
2328+
] {
2329+
supported_dialects_nulls.verified_stmt(sql);
2330+
}
2331+
}
2332+
22902333
#[test]
22912334
fn parse_create_table() {
22922335
let sql = "CREATE TABLE uk_cities (\
@@ -3346,6 +3389,7 @@ fn parse_scalar_function_in_projection() {
33463389
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
33473390
Expr::Identifier(Ident::new("id"))
33483391
))],
3392+
null_treatment: None,
33493393
filter: None,
33503394
over: None,
33513395
distinct: false,
@@ -3466,6 +3510,7 @@ fn parse_named_argument_function() {
34663510
))),
34673511
},
34683512
],
3513+
null_treatment: None,
34693514
filter: None,
34703515
over: None,
34713516
distinct: false,
@@ -3498,6 +3543,7 @@ fn parse_window_functions() {
34983543
&Expr::Function(Function {
34993544
name: ObjectName(vec![Ident::new("row_number")]),
35003545
args: vec![],
3546+
null_treatment: None,
35013547
filter: None,
35023548
over: Some(WindowType::WindowSpec(WindowSpec {
35033549
partition_by: vec![],
@@ -3542,6 +3588,7 @@ fn test_parse_named_window() {
35423588
quote_style: None,
35433589
}),
35443590
))],
3591+
null_treatment: None,
35453592
filter: None,
35463593
over: Some(WindowType::NamedWindow(Ident {
35473594
value: "window1".to_string(),
@@ -3568,6 +3615,7 @@ fn test_parse_named_window() {
35683615
quote_style: None,
35693616
}),
35703617
))],
3618+
null_treatment: None,
35713619
filter: None,
35723620
over: Some(WindowType::NamedWindow(Ident {
35733621
value: "window2".to_string(),
@@ -4038,6 +4086,7 @@ fn parse_at_timezone() {
40384086
quote_style: None,
40394087
}]),
40404088
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero.clone()))],
4089+
null_treatment: None,
40414090
filter: None,
40424091
over: None,
40434092
distinct: false,
@@ -4066,6 +4115,7 @@ fn parse_at_timezone() {
40664115
quote_style: None,
40674116
},],),
40684117
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(zero))],
4118+
null_treatment: None,
40694119
filter: None,
40704120
over: None,
40714121
distinct: false,
@@ -4078,6 +4128,7 @@ fn parse_at_timezone() {
40784128
Value::SingleQuotedString("%Y-%m-%dT%H".to_string()),
40794129
),),),
40804130
],
4131+
null_treatment: None,
40814132
filter: None,
40824133
over: None,
40834134
distinct: false,
@@ -4237,6 +4288,7 @@ fn parse_table_function() {
42374288
args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(
42384289
Value::SingleQuotedString("1".to_owned()),
42394290
)))],
4291+
null_treatment: None,
42404292
filter: None,
42414293
over: None,
42424294
distinct: false,
@@ -4389,6 +4441,7 @@ fn parse_unnest_in_from_clause() {
43894441
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))),
43904442
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("3")))),
43914443
],
4444+
null_treatment: None,
43924445
filter: None,
43934446
over: None,
43944447
distinct: false,
@@ -4419,6 +4472,7 @@ fn parse_unnest_in_from_clause() {
44194472
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("2")))),
44204473
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("3")))),
44214474
],
4475+
null_treatment: None,
44224476
filter: None,
44234477
over: None,
44244478
distinct: false,
@@ -4431,6 +4485,7 @@ fn parse_unnest_in_from_clause() {
44314485
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("5")))),
44324486
FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value(number("6")))),
44334487
],
4488+
null_treatment: None,
44344489
filter: None,
44354490
over: None,
44364491
distinct: false,
@@ -6904,6 +6959,7 @@ fn parse_time_functions() {
69046959
let select_localtime_func_call_ast = Function {
69056960
name: ObjectName(vec![Ident::new(func_name)]),
69066961
args: vec![],
6962+
null_treatment: None,
69076963
filter: None,
69086964
over: None,
69096965
distinct: false,
@@ -7391,6 +7447,7 @@ fn parse_pivot_table() {
73917447
args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
73927448
Expr::CompoundIdentifier(vec![Ident::new("a"), Ident::new("amount"),])
73937449
))]),
7450+
null_treatment: None,
73947451
filter: None,
73957452
over: None,
73967453
distinct: false,
@@ -7541,6 +7598,7 @@ fn parse_pivot_unpivot_table() {
75417598
args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
75427599
Expr::Identifier(Ident::new("population"))
75437600
))]),
7601+
null_treatment: None,
75447602
filter: None,
75457603
over: None,
75467604
distinct: false,

tests/sqlparser_hive.rs

+1
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ fn parse_delimited_identifiers() {
361361
&Expr::Function(Function {
362362
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
363363
args: vec![],
364+
null_treatment: None,
364365
filter: None,
365366
over: None,
366367
distinct: false,

tests/sqlparser_mssql.rs

+1
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ fn parse_delimited_identifiers() {
334334
&Expr::Function(Function {
335335
name: ObjectName(vec![Ident::with_quote('"', "myfun")]),
336336
args: vec![],
337+
null_treatment: None,
337338
filter: None,
338339
over: None,
339340
distinct: false,

0 commit comments

Comments
 (0)