Skip to content

Commit 41eb491

Browse files
committed
Add support for INCLUDE/EXCLUDE NULLS for UNPIVOT
1 parent ed41654 commit 41eb491

File tree

4 files changed

+93
-42
lines changed

4 files changed

+93
-42
lines changed

src/ast/query.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,7 +1248,7 @@ pub enum TableFactor {
12481248
///
12491249
/// Syntax:
12501250
/// ```sql
1251-
/// table UNPIVOT(value FOR name IN (column1, [ column2, ... ])) [ alias ]
1251+
/// table UNPIVOT [ { INCLUDE | EXCLUDE } NULLS ] (value FOR name IN (column1, [ column2, ... ])) [ alias ]
12521252
/// ```
12531253
///
12541254
/// See <https://docs.snowflake.com/en/sql-reference/constructs/unpivot>.
@@ -1257,6 +1257,7 @@ pub enum TableFactor {
12571257
value: Ident,
12581258
name: Ident,
12591259
columns: Vec<Ident>,
1260+
include_nulls: Option<bool>,
12601261
alias: Option<TableAlias>,
12611262
},
12621263
/// A `MATCH_RECOGNIZE` operation on a table.
@@ -1893,15 +1894,23 @@ impl fmt::Display for TableFactor {
18931894
}
18941895
TableFactor::Unpivot {
18951896
table,
1897+
include_nulls,
18961898
value,
18971899
name,
18981900
columns,
18991901
alias,
19001902
} => {
1903+
write!(f, "{table} UNPIVOT")?;
1904+
if let Some(include_nulls) = include_nulls {
1905+
if *include_nulls {
1906+
write!(f, " INCLUDE NULLS ")?;
1907+
} else {
1908+
write!(f, " EXCLUDE NULLS ")?;
1909+
}
1910+
}
19011911
write!(
19021912
f,
1903-
"{} UNPIVOT({} FOR {} IN ({}))",
1904-
table,
1913+
"({} FOR {} IN ({}))",
19051914
value,
19061915
name,
19071916
display_comma_separated(columns)

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,7 @@ impl Spanned for TableFactor {
18301830
TableFactor::Unpivot {
18311831
table,
18321832
value,
1833+
include_nulls: _,
18331834
name,
18341835
columns,
18351836
alias,

src/parser/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12396,6 +12396,15 @@ impl<'a> Parser<'a> {
1239612396
&mut self,
1239712397
table: TableFactor,
1239812398
) -> Result<TableFactor, ParserError> {
12399+
let include_nulls = if self.parse_keyword(Keyword::INCLUDE) {
12400+
self.expect_keyword_is(Keyword::NULLS)?;
12401+
Some(true)
12402+
} else if self.parse_keyword(Keyword::EXCLUDE) {
12403+
self.expect_keyword_is(Keyword::NULLS)?;
12404+
Some(false)
12405+
} else {
12406+
None
12407+
};
1239912408
self.expect_token(&Token::LParen)?;
1240012409
let value = self.parse_identifier()?;
1240112410
self.expect_keyword_is(Keyword::FOR)?;
@@ -12407,6 +12416,7 @@ impl<'a> Parser<'a> {
1240712416
Ok(TableFactor::Unpivot {
1240812417
table: Box::new(table),
1240912418
value,
12419+
include_nulls,
1241012420
name,
1241112421
columns,
1241212422
alias,

tests/sqlparser_common.rs

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10568,49 +10568,47 @@ fn parse_unpivot_table() {
1056810568
"SELECT * FROM sales AS s ",
1056910569
"UNPIVOT(quantity FOR quarter IN (Q1, Q2, Q3, Q4)) AS u (product, quarter, quantity)"
1057010570
);
10571-
10572-
pretty_assertions::assert_eq!(
10573-
verified_only_select(sql).from[0].relation,
10574-
Unpivot {
10575-
table: Box::new(TableFactor::Table {
10576-
name: ObjectName::from(vec![Ident::new("sales")]),
10577-
alias: Some(TableAlias {
10578-
name: Ident::new("s"),
10579-
columns: vec![]
10580-
}),
10581-
args: None,
10582-
with_hints: vec![],
10583-
version: None,
10584-
partitions: vec![],
10585-
with_ordinality: false,
10586-
json_path: None,
10587-
sample: None,
10588-
index_hints: vec![],
10571+
let base_unpivot = Unpivot {
10572+
table: Box::new(TableFactor::Table {
10573+
name: ObjectName::from(vec![Ident::new("sales")]),
10574+
alias: Some(TableAlias {
10575+
name: Ident::new("s"),
10576+
columns: vec![],
1058910577
}),
10590-
value: Ident {
10591-
value: "quantity".to_string(),
10592-
quote_style: None,
10593-
span: Span::empty()
10594-
},
10578+
args: None,
10579+
with_hints: vec![],
10580+
version: None,
10581+
partitions: vec![],
10582+
with_ordinality: false,
10583+
json_path: None,
10584+
sample: None,
10585+
index_hints: vec![],
10586+
}),
10587+
include_nulls: None,
10588+
value: Ident {
10589+
value: "quantity".to_string(),
10590+
quote_style: None,
10591+
span: Span::empty(),
10592+
},
1059510593

10596-
name: Ident {
10597-
value: "quarter".to_string(),
10598-
quote_style: None,
10599-
span: Span::empty()
10600-
},
10601-
columns: ["Q1", "Q2", "Q3", "Q4"]
10594+
name: Ident {
10595+
value: "quarter".to_string(),
10596+
quote_style: None,
10597+
span: Span::empty(),
10598+
},
10599+
columns: ["Q1", "Q2", "Q3", "Q4"]
10600+
.into_iter()
10601+
.map(Ident::new)
10602+
.collect(),
10603+
alias: Some(TableAlias {
10604+
name: Ident::new("u"),
10605+
columns: ["product", "quarter", "quantity"]
1060210606
.into_iter()
10603-
.map(Ident::new)
10607+
.map(TableAliasColumnDef::from_name)
1060410608
.collect(),
10605-
alias: Some(TableAlias {
10606-
name: Ident::new("u"),
10607-
columns: ["product", "quarter", "quantity"]
10608-
.into_iter()
10609-
.map(TableAliasColumnDef::from_name)
10610-
.collect(),
10611-
}),
10612-
}
10613-
);
10609+
}),
10610+
};
10611+
pretty_assertions::assert_eq!(verified_only_select(sql).from[0].relation, base_unpivot);
1061410612
assert_eq!(verified_stmt(sql).to_string(), sql);
1061510613

1061610614
let sql_without_aliases = concat!(
@@ -10630,6 +10628,38 @@ fn parse_unpivot_table() {
1063010628
verified_stmt(sql_without_aliases).to_string(),
1063110629
sql_without_aliases
1063210630
);
10631+
10632+
let sql_unpivot_exclude_nulls = concat!(
10633+
"SELECT * FROM sales AS s ",
10634+
"UNPIVOT EXCLUDE NULLS (quantity FOR quarter IN (Q1, Q2, Q3, Q4)) AS u (product, quarter, quantity)"
10635+
);
10636+
10637+
if let Unpivot { include_nulls, .. } =
10638+
&verified_only_select(sql_unpivot_exclude_nulls).from[0].relation
10639+
{
10640+
assert_eq!(*include_nulls, Some(false));
10641+
}
10642+
10643+
assert_eq!(
10644+
verified_stmt(sql_unpivot_exclude_nulls).to_string(),
10645+
sql_unpivot_exclude_nulls
10646+
);
10647+
10648+
let sql_unpivot_include_nulls = concat!(
10649+
"SELECT * FROM sales AS s ",
10650+
"UNPIVOT INCLUDE NULLS (quantity FOR quarter IN (Q1, Q2, Q3, Q4)) AS u (product, quarter, quantity)"
10651+
);
10652+
10653+
if let Unpivot { include_nulls, .. } =
10654+
&verified_only_select(sql_unpivot_include_nulls).from[0].relation
10655+
{
10656+
assert_eq!(*include_nulls, Some(true));
10657+
}
10658+
10659+
assert_eq!(
10660+
verified_stmt(sql_unpivot_include_nulls).to_string(),
10661+
sql_unpivot_include_nulls
10662+
);
1063310663
}
1063410664

1063510665
#[test]
@@ -10726,6 +10756,7 @@ fn parse_pivot_unpivot_table() {
1072610756
sample: None,
1072710757
index_hints: vec![],
1072810758
}),
10759+
include_nulls: None,
1072910760
value: Ident {
1073010761
value: "population".to_string(),
1073110762
quote_style: None,

0 commit comments

Comments
 (0)