Skip to content

Commit 773d6c7

Browse files
authored
Merge cddb87d into 8626051
2 parents 8626051 + cddb87d commit 773d6c7

File tree

6 files changed

+204
-37
lines changed

6 files changed

+204
-37
lines changed

src/ast/mod.rs

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@
1313
//! SQL Abstract Syntax Tree (AST) types
1414
#[cfg(not(feature = "std"))]
1515
use alloc::{
16+
borrow::Cow,
1617
boxed::Box,
1718
format,
1819
string::{String, ToString},
1920
vec::Vec,
2021
};
22+
23+
#[cfg(feature = "std")]
24+
use std::borrow::Cow;
25+
2126
use core::fmt::{self, Display};
2227

2328
#[cfg(feature = "serde")]
@@ -1406,6 +1411,35 @@ impl fmt::Display for NullTreatment {
14061411
}
14071412
}
14081413

1414+
/// Specifies Ignore / Respect NULL within window functions.
1415+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1416+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1417+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1418+
pub enum NullTreatmentType {
1419+
/// The declaration is part of the function's arguments.
1420+
///
1421+
/// ```sql
1422+
/// FIRST_VALUE(x IGNORE NULLS) OVER ()
1423+
/// ```
1424+
FunctionArg(NullTreatment),
1425+
/// The declaration occurs after the function call.
1426+
///
1427+
/// ```sql
1428+
/// FIRST_VALUE(x) IGNORE NULLS OVER ()
1429+
/// ```
1430+
AfterFunction(NullTreatment),
1431+
}
1432+
1433+
impl Display for NullTreatmentType {
1434+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1435+
let null_treatment = match self {
1436+
NullTreatmentType::FunctionArg(n) => n,
1437+
NullTreatmentType::AfterFunction(n) => n,
1438+
};
1439+
write!(f, "{null_treatment}")
1440+
}
1441+
}
1442+
14091443
/// Specifies [WindowFrame]'s `start_bound` and `end_bound`
14101444
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
14111445
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -4787,15 +4821,18 @@ pub struct Function {
47874821
pub args: Vec<FunctionArg>,
47884822
/// e.g. `x > 5` in `COUNT(x) FILTER (WHERE x > 5)`
47894823
pub filter: Option<Box<Expr>>,
4790-
// Snowflake/MSSQL supports different options for null treatment in rank functions
4791-
pub null_treatment: Option<NullTreatment>,
4824+
/// Specifies Ignore / Respect NULL within window functions.
4825+
///
4826+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions#first_value)
4827+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/functions/first_value)
4828+
pub null_treatment: Option<NullTreatmentType>,
47924829
pub over: Option<WindowType>,
4793-
// aggregate functions may specify eg `COUNT(DISTINCT x)`
4830+
/// aggregate functions may specify eg `COUNT(DISTINCT x)`
47944831
pub distinct: bool,
4795-
// Some functions must be called without trailing parentheses, for example Postgres
4796-
// do it for current_catalog, current_schema, etc. This flags is used for formatting.
4832+
/// Some functions must be called without trailing parentheses, for example Postgres
4833+
/// do it for current_catalog, current_schema, etc. This flags is used for formatting.
47974834
pub special: bool,
4798-
// Required ordering for the function (if empty, there is no requirement).
4835+
/// Required ordering for the function (if empty, there is no requirement).
47994836
pub order_by: Vec<OrderByExpr>,
48004837
}
48014838

@@ -4830,19 +4867,25 @@ impl fmt::Display for Function {
48304867
};
48314868
write!(
48324869
f,
4833-
"{}({}{}{order_by}{})",
4870+
"{}({}{}{order_by}{}{})",
48344871
self.name,
48354872
if self.distinct { "DISTINCT " } else { "" },
48364873
display_comma_separated(&self.args),
48374874
display_comma_separated(&self.order_by),
4875+
match self.null_treatment {
4876+
Some(NullTreatmentType::FunctionArg(null_treatment)) => {
4877+
Cow::from(format!(" {null_treatment}"))
4878+
}
4879+
_ => Cow::from(""),
4880+
}
48384881
)?;
48394882

48404883
if let Some(filter_cond) = &self.filter {
48414884
write!(f, " FILTER (WHERE {filter_cond})")?;
48424885
}
48434886

4844-
if let Some(o) = &self.null_treatment {
4845-
write!(f, " {o}")?;
4887+
if let Some(NullTreatmentType::AfterFunction(null_treatment)) = &self.null_treatment {
4888+
write!(f, " {null_treatment}")?;
48464889
}
48474890

48484891
if let Some(o) = &self.over {

src/dialect/bigquery.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ impl Dialect for BigQueryDialect {
3030
ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_'
3131
}
3232

33+
/// See [doc](https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions#first_value)
34+
fn supports_window_function_null_treatment_arg(&self) -> bool {
35+
true
36+
}
37+
3338
// See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#escape_sequences
3439
fn supports_string_literal_backslash_escape(&self) -> bool {
3540
true

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ impl Dialect for GenericDialect {
5151
true
5252
}
5353

54+
fn supports_window_function_null_treatment_arg(&self) -> bool {
55+
true
56+
}
57+
5458
fn supports_dictionary_syntax(&self) -> bool {
5559
true
5660
}

src/dialect/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,20 @@ pub trait Dialect: Debug + Any {
185185
fn supports_named_fn_args_with_eq_operator(&self) -> bool {
186186
false
187187
}
188+
/// Returns true if the dialects supports specifying null treatment
189+
/// as part of a window function's parameter list. As opposed
190+
/// to after the parameter list.
191+
/// i.e The following syntax returns true
192+
/// ```sql
193+
/// FIRST_VALUE(a IGNORE NULLS) OVER ()
194+
/// ```
195+
/// while the following syntax returns false
196+
/// ```sql
197+
/// FIRST_VALUE(a) IGNORE NULLS OVER ()
198+
/// ```
199+
fn supports_window_function_null_treatment_arg(&self) -> bool {
200+
false
201+
}
188202
/// Returns true if the dialect supports defining structs or objects using a
189203
/// syntax like `{'x': 1, 'y': 2, 'z': 3}`.
190204
fn supports_dictionary_syntax(&self) -> bool {

src/parser/mod.rs

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@ impl From<bool> for MatchedTrailingBracket {
208208
}
209209
}
210210

211+
/// Output of the [`Parser::parse_window_function_args`] function.
212+
struct ParseWindowFunctionArgsOutput {
213+
args: Vec<FunctionArg>,
214+
order_by: Vec<OrderByExpr>,
215+
null_treatment: Option<NullTreatment>,
216+
}
217+
211218
/// Options that control how the [`Parser`] parses SQL text
212219
#[derive(Debug, Clone, PartialEq, Eq)]
213220
pub struct ParserOptions {
@@ -1229,7 +1236,11 @@ impl<'a> Parser<'a> {
12291236
pub fn parse_function(&mut self, name: ObjectName) -> Result<Expr, ParserError> {
12301237
self.expect_token(&Token::LParen)?;
12311238
let distinct = self.parse_all_or_distinct()?.is_some();
1232-
let (args, order_by) = self.parse_optional_args_with_orderby()?;
1239+
let ParseWindowFunctionArgsOutput {
1240+
args,
1241+
order_by,
1242+
null_treatment,
1243+
} = self.parse_window_function_args()?;
12331244
let filter = if self.dialect.supports_filter_during_aggregation()
12341245
&& self.parse_keyword(Keyword::FILTER)
12351246
&& self.consume_token(&Token::LParen)
@@ -1241,19 +1252,15 @@ impl<'a> Parser<'a> {
12411252
} else {
12421253
None
12431254
};
1244-
let null_treatment = match self.parse_one_of_keywords(&[Keyword::RESPECT, Keyword::IGNORE])
1245-
{
1246-
Some(keyword) => {
1247-
self.expect_keyword(Keyword::NULLS)?;
12481255

1249-
match keyword {
1250-
Keyword::RESPECT => Some(NullTreatment::RespectNulls),
1251-
Keyword::IGNORE => Some(NullTreatment::IgnoreNulls),
1252-
_ => None,
1253-
}
1254-
}
1255-
None => None,
1256-
};
1256+
// Syntax for null treatment shows up either in the args list
1257+
// or after the function call, but not both.
1258+
let mut null_treatment = null_treatment.map(NullTreatmentType::FunctionArg);
1259+
if null_treatment.is_none() {
1260+
null_treatment = self
1261+
.parse_null_treatment()?
1262+
.map(NullTreatmentType::AfterFunction);
1263+
}
12571264
let over = if self.parse_keyword(Keyword::OVER) {
12581265
if self.consume_token(&Token::LParen) {
12591266
let window_spec = self.parse_window_spec()?;
@@ -1276,17 +1283,37 @@ impl<'a> Parser<'a> {
12761283
}))
12771284
}
12781285

1286+
/// Optionally parses a null treatment clause.
1287+
fn parse_null_treatment(&mut self) -> Result<Option<NullTreatment>, ParserError> {
1288+
match self.parse_one_of_keywords(&[Keyword::RESPECT, Keyword::IGNORE]) {
1289+
Some(keyword) => {
1290+
self.expect_keyword(Keyword::NULLS)?;
1291+
1292+
Ok(match keyword {
1293+
Keyword::RESPECT => Some(NullTreatment::RespectNulls),
1294+
Keyword::IGNORE => Some(NullTreatment::IgnoreNulls),
1295+
_ => None,
1296+
})
1297+
}
1298+
None => Ok(None),
1299+
}
1300+
}
1301+
12791302
pub fn parse_time_functions(&mut self, name: ObjectName) -> Result<Expr, ParserError> {
1280-
let (args, order_by, special) = if self.consume_token(&Token::LParen) {
1281-
let (args, order_by) = self.parse_optional_args_with_orderby()?;
1282-
(args, order_by, false)
1303+
let (args, order_by, null_treatment, special) = if self.consume_token(&Token::LParen) {
1304+
let ParseWindowFunctionArgsOutput {
1305+
args,
1306+
order_by,
1307+
null_treatment,
1308+
} = self.parse_window_function_args()?;
1309+
(args, order_by, null_treatment, false)
12831310
} else {
1284-
(vec![], vec![], true)
1311+
(vec![], vec![], None, true)
12851312
};
12861313
Ok(Expr::Function(Function {
12871314
name,
12881315
args,
1289-
null_treatment: None,
1316+
null_treatment: null_treatment.map(NullTreatmentType::FunctionArg),
12901317
filter: None,
12911318
over: None,
12921319
distinct: false,
@@ -9326,11 +9353,21 @@ impl<'a> Parser<'a> {
93269353
}
93279354
}
93289355

9329-
pub fn parse_optional_args_with_orderby(
9330-
&mut self,
9331-
) -> Result<(Vec<FunctionArg>, Vec<OrderByExpr>), ParserError> {
9356+
/// Parses a potentially empty list of arguments to a window function
9357+
/// (including the closing parenthesis).
9358+
///
9359+
/// Examples:
9360+
/// ```sql
9361+
/// FIRST_VALUE(x ORDER BY 1,2,3);
9362+
/// FIRST_VALUE(x IGNORE NULL);
9363+
/// ```
9364+
fn parse_window_function_args(&mut self) -> Result<ParseWindowFunctionArgsOutput, ParserError> {
93329365
if self.consume_token(&Token::RParen) {
9333-
Ok((vec![], vec![]))
9366+
Ok(ParseWindowFunctionArgsOutput {
9367+
args: vec![],
9368+
order_by: vec![],
9369+
null_treatment: None,
9370+
})
93349371
} else {
93359372
// Snowflake permits a subquery to be passed as an argument without
93369373
// an enclosing set of parens if it's the only argument.
@@ -9342,22 +9379,34 @@ impl<'a> Parser<'a> {
93429379
self.prev_token();
93439380
let subquery = self.parse_boxed_query()?;
93449381
self.expect_token(&Token::RParen)?;
9345-
return Ok((
9346-
vec![FunctionArg::Unnamed(FunctionArgExpr::from(Expr::Subquery(
9382+
return Ok(ParseWindowFunctionArgsOutput {
9383+
args: vec![FunctionArg::Unnamed(FunctionArgExpr::from(Expr::Subquery(
93479384
subquery,
93489385
)))],
9349-
vec![],
9350-
));
9386+
order_by: vec![],
9387+
null_treatment: None,
9388+
});
93519389
}
93529390

93539391
let args = self.parse_comma_separated(Parser::parse_function_args)?;
93549392
let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) {
93559393
self.parse_comma_separated(Parser::parse_order_by_expr)?
93569394
} else {
9357-
vec![]
9395+
Default::default()
9396+
};
9397+
9398+
let null_treatment = if self.dialect.supports_window_function_null_treatment_arg() {
9399+
self.parse_null_treatment()?
9400+
} else {
9401+
None
93589402
};
9403+
93599404
self.expect_token(&Token::RParen)?;
9360-
Ok((args, order_by))
9405+
Ok(ParseWindowFunctionArgsOutput {
9406+
args,
9407+
order_by,
9408+
null_treatment,
9409+
})
93619410
}
93629411
}
93639412

tests/sqlparser_common.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2644,6 +2644,58 @@ fn parse_window_rank_function() {
26442644
}
26452645
}
26462646

2647+
#[test]
2648+
fn parse_window_function_null_treatment_arg() {
2649+
let dialects = all_dialects_where(|d| d.supports_window_function_null_treatment_arg());
2650+
let sql = "SELECT \
2651+
FIRST_VALUE(a IGNORE NULLS) OVER (), \
2652+
FIRST_VALUE(b RESPECT NULLS) OVER () \
2653+
FROM mytable";
2654+
let Select { projection, .. } = dialects.verified_only_select(sql);
2655+
for (i, (expected_expr, expected_null_treatment)) in [
2656+
("a", NullTreatment::IgnoreNulls),
2657+
("b", NullTreatment::RespectNulls),
2658+
]
2659+
.into_iter()
2660+
.enumerate()
2661+
{
2662+
let SelectItem::UnnamedExpr(Expr::Function(actual)) = &projection[i] else {
2663+
unreachable!()
2664+
};
2665+
assert_eq!(ObjectName(vec![Ident::new("FIRST_VALUE")]), actual.name);
2666+
assert!(actual.order_by.is_empty());
2667+
assert_eq!(1, actual.args.len());
2668+
let FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Identifier(actual_expr))) =
2669+
&actual.args[0]
2670+
else {
2671+
unreachable!()
2672+
};
2673+
assert_eq!(&Ident::new(expected_expr), actual_expr);
2674+
let Some(NullTreatmentType::FunctionArg(actual_null_treatment)) = actual.null_treatment
2675+
else {
2676+
unreachable!()
2677+
};
2678+
assert_eq!(expected_null_treatment, actual_null_treatment);
2679+
}
2680+
2681+
let sql = "SELECT FIRST_VALUE(a ORDER BY b IGNORE NULLS) OVER () FROM t1";
2682+
dialects.verified_stmt(sql);
2683+
2684+
let sql = "SELECT LAG(1 IGNORE NULLS) IGNORE NULLS OVER () FROM t1";
2685+
assert_eq!(
2686+
dialects.parse_sql_statements(sql).unwrap_err(),
2687+
ParserError::ParserError("Expected end of statement, found: NULLS".to_string())
2688+
);
2689+
2690+
let sql = "SELECT LAG(1 IGNORE NULLS) IGNORE NULLS OVER () FROM t1";
2691+
assert_eq!(
2692+
all_dialects_where(|d| !d.supports_window_function_null_treatment_arg())
2693+
.parse_sql_statements(sql)
2694+
.unwrap_err(),
2695+
ParserError::ParserError("Expected ), found: IGNORE".to_string())
2696+
);
2697+
}
2698+
26472699
#[test]
26482700
fn parse_create_table() {
26492701
let sql = "CREATE TABLE uk_cities (\

0 commit comments

Comments
 (0)