Skip to content

Commit 16856ae

Browse files
committed
Add support for INCLUDE/EXCLUDE NULLS for UNPIVOT
1 parent 0d2976d commit 16856ae

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
@@ -1239,7 +1239,7 @@ pub enum TableFactor {
12391239
///
12401240
/// Syntax:
12411241
/// ```sql
1242-
/// table UNPIVOT(value FOR name IN (column1, [ column2, ... ])) [ alias ]
1242+
/// table UNPIVOT [ { INCLUDE | EXCLUDE } NULLS ] (value FOR name IN (column1, [ column2, ... ])) [ alias ]
12431243
/// ```
12441244
///
12451245
/// See <https://docs.snowflake.com/en/sql-reference/constructs/unpivot>.
@@ -1248,6 +1248,7 @@ pub enum TableFactor {
12481248
value: Ident,
12491249
name: Ident,
12501250
columns: Vec<Ident>,
1251+
include_nulls: Option<bool>,
12511252
alias: Option<TableAlias>,
12521253
},
12531254
/// A `MATCH_RECOGNIZE` operation on a table.
@@ -1884,15 +1885,23 @@ impl fmt::Display for TableFactor {
18841885
}
18851886
TableFactor::Unpivot {
18861887
table,
1888+
include_nulls,
18871889
value,
18881890
name,
18891891
columns,
18901892
alias,
18911893
} => {
1894+
write!(f, "{table} UNPIVOT")?;
1895+
if let Some(include_nulls) = include_nulls {
1896+
if *include_nulls {
1897+
write!(f, " INCLUDE NULLS ")?;
1898+
} else {
1899+
write!(f, " EXCLUDE NULLS ")?;
1900+
}
1901+
}
18921902
write!(
18931903
f,
1894-
"{} UNPIVOT({} FOR {} IN ({}))",
1895-
table,
1904+
"({} FOR {} IN ({}))",
18961905
value,
18971906
name,
18981907
display_comma_separated(columns)

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1924,6 +1924,7 @@ impl Spanned for TableFactor {
19241924
TableFactor::Unpivot {
19251925
table,
19261926
value,
1927+
include_nulls: _,
19271928
name,
19281929
columns,
19291930
alias,

src/parser/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12744,6 +12744,15 @@ impl<'a> Parser<'a> {
1274412744
&mut self,
1274512745
table: TableFactor,
1274612746
) -> Result<TableFactor, ParserError> {
12747+
let include_nulls = if self.parse_keyword(Keyword::INCLUDE) {
12748+
self.expect_keyword_is(Keyword::NULLS)?;
12749+
Some(true)
12750+
} else if self.parse_keyword(Keyword::EXCLUDE) {
12751+
self.expect_keyword_is(Keyword::NULLS)?;
12752+
Some(false)
12753+
} else {
12754+
None
12755+
};
1274712756
self.expect_token(&Token::LParen)?;
1274812757
let value = self.parse_identifier()?;
1274912758
self.expect_keyword_is(Keyword::FOR)?;
@@ -12755,6 +12764,7 @@ impl<'a> Parser<'a> {
1275512764
Ok(TableFactor::Unpivot {
1275612765
table: Box::new(table),
1275712766
value,
12767+
include_nulls,
1275812768
name,
1275912769
columns,
1276012770
alias,

tests/sqlparser_common.rs

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10674,49 +10674,47 @@ fn parse_unpivot_table() {
1067410674
"SELECT * FROM sales AS s ",
1067510675
"UNPIVOT(quantity FOR quarter IN (Q1, Q2, Q3, Q4)) AS u (product, quarter, quantity)"
1067610676
);
10677-
10678-
pretty_assertions::assert_eq!(
10679-
verified_only_select(sql).from[0].relation,
10680-
Unpivot {
10681-
table: Box::new(TableFactor::Table {
10682-
name: ObjectName::from(vec![Ident::new("sales")]),
10683-
alias: Some(TableAlias {
10684-
name: Ident::new("s"),
10685-
columns: vec![]
10686-
}),
10687-
args: None,
10688-
with_hints: vec![],
10689-
version: None,
10690-
partitions: vec![],
10691-
with_ordinality: false,
10692-
json_path: None,
10693-
sample: None,
10694-
index_hints: vec![],
10677+
let base_unpivot = Unpivot {
10678+
table: Box::new(TableFactor::Table {
10679+
name: ObjectName::from(vec![Ident::new("sales")]),
10680+
alias: Some(TableAlias {
10681+
name: Ident::new("s"),
10682+
columns: vec![],
1069510683
}),
10696-
value: Ident {
10697-
value: "quantity".to_string(),
10698-
quote_style: None,
10699-
span: Span::empty()
10700-
},
10684+
args: None,
10685+
with_hints: vec![],
10686+
version: None,
10687+
partitions: vec![],
10688+
with_ordinality: false,
10689+
json_path: None,
10690+
sample: None,
10691+
index_hints: vec![],
10692+
}),
10693+
include_nulls: None,
10694+
value: Ident {
10695+
value: "quantity".to_string(),
10696+
quote_style: None,
10697+
span: Span::empty(),
10698+
},
1070110699

10702-
name: Ident {
10703-
value: "quarter".to_string(),
10704-
quote_style: None,
10705-
span: Span::empty()
10706-
},
10707-
columns: ["Q1", "Q2", "Q3", "Q4"]
10700+
name: Ident {
10701+
value: "quarter".to_string(),
10702+
quote_style: None,
10703+
span: Span::empty(),
10704+
},
10705+
columns: ["Q1", "Q2", "Q3", "Q4"]
10706+
.into_iter()
10707+
.map(Ident::new)
10708+
.collect(),
10709+
alias: Some(TableAlias {
10710+
name: Ident::new("u"),
10711+
columns: ["product", "quarter", "quantity"]
1070810712
.into_iter()
10709-
.map(Ident::new)
10713+
.map(TableAliasColumnDef::from_name)
1071010714
.collect(),
10711-
alias: Some(TableAlias {
10712-
name: Ident::new("u"),
10713-
columns: ["product", "quarter", "quantity"]
10714-
.into_iter()
10715-
.map(TableAliasColumnDef::from_name)
10716-
.collect(),
10717-
}),
10718-
}
10719-
);
10715+
}),
10716+
};
10717+
pretty_assertions::assert_eq!(verified_only_select(sql).from[0].relation, base_unpivot);
1072010718
assert_eq!(verified_stmt(sql).to_string(), sql);
1072110719

1072210720
let sql_without_aliases = concat!(
@@ -10736,6 +10734,38 @@ fn parse_unpivot_table() {
1073610734
verified_stmt(sql_without_aliases).to_string(),
1073710735
sql_without_aliases
1073810736
);
10737+
10738+
let sql_unpivot_exclude_nulls = concat!(
10739+
"SELECT * FROM sales AS s ",
10740+
"UNPIVOT EXCLUDE NULLS (quantity FOR quarter IN (Q1, Q2, Q3, Q4)) AS u (product, quarter, quantity)"
10741+
);
10742+
10743+
if let Unpivot { include_nulls, .. } =
10744+
&verified_only_select(sql_unpivot_exclude_nulls).from[0].relation
10745+
{
10746+
assert_eq!(*include_nulls, Some(false));
10747+
}
10748+
10749+
assert_eq!(
10750+
verified_stmt(sql_unpivot_exclude_nulls).to_string(),
10751+
sql_unpivot_exclude_nulls
10752+
);
10753+
10754+
let sql_unpivot_include_nulls = concat!(
10755+
"SELECT * FROM sales AS s ",
10756+
"UNPIVOT INCLUDE NULLS (quantity FOR quarter IN (Q1, Q2, Q3, Q4)) AS u (product, quarter, quantity)"
10757+
);
10758+
10759+
if let Unpivot { include_nulls, .. } =
10760+
&verified_only_select(sql_unpivot_include_nulls).from[0].relation
10761+
{
10762+
assert_eq!(*include_nulls, Some(true));
10763+
}
10764+
10765+
assert_eq!(
10766+
verified_stmt(sql_unpivot_include_nulls).to_string(),
10767+
sql_unpivot_include_nulls
10768+
);
1073910769
}
1074010770

1074110771
#[test]
@@ -10832,6 +10862,7 @@ fn parse_pivot_unpivot_table() {
1083210862
sample: None,
1083310863
index_hints: vec![],
1083410864
}),
10865+
include_nulls: None,
1083510866
value: Ident {
1083610867
value: "population".to_string(),
1083710868
quote_style: None,

0 commit comments

Comments
 (0)