Skip to content

Commit d5bc356

Browse files
chenkovskyayman-sigma
authored andcommitted
feat: support multi value columns and aliases in unpivot (apache#1969)
1 parent d27167c commit d5bc356

4 files changed

Lines changed: 144 additions & 30 deletions

File tree

src/ast/query.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,11 +1355,12 @@ pub enum TableFactor {
13551355
/// ```
13561356
///
13571357
/// See <https://docs.snowflake.com/en/sql-reference/constructs/unpivot>.
1358+
/// See <https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot>.
13581359
Unpivot {
13591360
table: Box<TableFactor>,
1360-
value: Ident,
1361+
value: Expr,
13611362
name: Ident,
1362-
columns: Vec<Ident>,
1363+
columns: Vec<ExprWithAlias>,
13631364
null_inclusion: Option<NullInclusion>,
13641365
alias: Option<TableAlias>,
13651366
},

src/ast/spans.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2011,9 +2011,9 @@ impl Spanned for TableFactor {
20112011
alias,
20122012
} => union_spans(
20132013
core::iter::once(table.span())
2014-
.chain(core::iter::once(value.span))
2014+
.chain(core::iter::once(value.span()))
20152015
.chain(core::iter::once(name.span))
2016-
.chain(columns.iter().map(|i| i.span))
2016+
.chain(columns.iter().map(|ilist| ilist.span()))
20172017
.chain(alias.as_ref().map(|alias| alias.span())),
20182018
),
20192019
TableFactor::MatchRecognize {

src/parser/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14065,11 +14065,13 @@ impl<'a> Parser<'a> {
1406514065
None
1406614066
};
1406714067
self.expect_token(&Token::LParen)?;
14068-
let value = self.parse_identifier()?;
14068+
let value = self.parse_expr()?;
1406914069
self.expect_keyword_is(Keyword::FOR)?;
1407014070
let name = self.parse_identifier()?;
1407114071
self.expect_keyword_is(Keyword::IN)?;
14072-
let columns = self.parse_parenthesized_column_list(Mandatory, false)?;
14072+
let columns = self.parse_parenthesized_column_list_inner(Mandatory, false, |p| {
14073+
p.parse_expr_with_alias()
14074+
})?;
1407314075
self.expect_token(&Token::RParen)?;
1407414076
let alias = self.maybe_parse_table_alias()?;
1407514077
Ok(TableFactor::Unpivot {

tests/sqlparser_common.rs

Lines changed: 135 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11012,20 +11012,14 @@ fn parse_unpivot_table() {
1101211012
index_hints: vec![],
1101311013
}),
1101411014
null_inclusion: None,
11015-
value: Ident {
11016-
value: "quantity".to_string(),
11017-
quote_style: None,
11018-
span: Span::empty(),
11019-
},
11020-
11021-
name: Ident {
11022-
value: "quarter".to_string(),
11023-
quote_style: None,
11024-
span: Span::empty(),
11025-
},
11015+
value: Expr::Identifier(Ident::new("quantity")),
11016+
name: Ident::new("quarter"),
1102611017
columns: ["Q1", "Q2", "Q3", "Q4"]
1102711018
.into_iter()
11028-
.map(Ident::new)
11019+
.map(|col| ExprWithAlias {
11020+
expr: Expr::Identifier(Ident::new(col)),
11021+
alias: None,
11022+
})
1102911023
.collect(),
1103011024
alias: Some(TableAlias {
1103111025
name: Ident::new("u"),
@@ -11087,6 +11081,129 @@ fn parse_unpivot_table() {
1108711081
verified_stmt(sql_unpivot_include_nulls).to_string(),
1108811082
sql_unpivot_include_nulls
1108911083
);
11084+
11085+
let sql_unpivot_with_alias = concat!(
11086+
"SELECT * FROM sales AS s ",
11087+
"UNPIVOT INCLUDE NULLS ",
11088+
"(quantity FOR quarter IN ",
11089+
"(Q1 AS Quater1, Q2 AS Quater2, Q3 AS Quater3, Q4 AS Quater4)) ",
11090+
"AS u (product, quarter, quantity)"
11091+
);
11092+
11093+
if let Unpivot { value, columns, .. } =
11094+
&verified_only_select(sql_unpivot_with_alias).from[0].relation
11095+
{
11096+
assert_eq!(
11097+
*columns,
11098+
vec![
11099+
ExprWithAlias {
11100+
expr: Expr::Identifier(Ident::new("Q1")),
11101+
alias: Some(Ident::new("Quater1")),
11102+
},
11103+
ExprWithAlias {
11104+
expr: Expr::Identifier(Ident::new("Q2")),
11105+
alias: Some(Ident::new("Quater2")),
11106+
},
11107+
ExprWithAlias {
11108+
expr: Expr::Identifier(Ident::new("Q3")),
11109+
alias: Some(Ident::new("Quater3")),
11110+
},
11111+
ExprWithAlias {
11112+
expr: Expr::Identifier(Ident::new("Q4")),
11113+
alias: Some(Ident::new("Quater4")),
11114+
},
11115+
]
11116+
);
11117+
assert_eq!(*value, Expr::Identifier(Ident::new("quantity")));
11118+
}
11119+
11120+
assert_eq!(
11121+
verified_stmt(sql_unpivot_with_alias).to_string(),
11122+
sql_unpivot_with_alias
11123+
);
11124+
11125+
let sql_unpivot_with_alias_and_multi_value = concat!(
11126+
"SELECT * FROM sales AS s ",
11127+
"UNPIVOT INCLUDE NULLS ((first_quarter, second_quarter) ",
11128+
"FOR half_of_the_year IN (",
11129+
"(Q1, Q2) AS H1, ",
11130+
"(Q3, Q4) AS H2",
11131+
"))"
11132+
);
11133+
11134+
if let Unpivot { value, columns, .. } =
11135+
&verified_only_select(sql_unpivot_with_alias_and_multi_value).from[0].relation
11136+
{
11137+
assert_eq!(
11138+
*columns,
11139+
vec![
11140+
ExprWithAlias {
11141+
expr: Expr::Tuple(vec![
11142+
Expr::Identifier(Ident::new("Q1")),
11143+
Expr::Identifier(Ident::new("Q2")),
11144+
]),
11145+
alias: Some(Ident::new("H1")),
11146+
},
11147+
ExprWithAlias {
11148+
expr: Expr::Tuple(vec![
11149+
Expr::Identifier(Ident::new("Q3")),
11150+
Expr::Identifier(Ident::new("Q4")),
11151+
]),
11152+
alias: Some(Ident::new("H2")),
11153+
},
11154+
]
11155+
);
11156+
assert_eq!(
11157+
*value,
11158+
Expr::Tuple(vec![
11159+
Expr::Identifier(Ident::new("first_quarter")),
11160+
Expr::Identifier(Ident::new("second_quarter")),
11161+
])
11162+
);
11163+
}
11164+
11165+
assert_eq!(
11166+
verified_stmt(sql_unpivot_with_alias_and_multi_value).to_string(),
11167+
sql_unpivot_with_alias_and_multi_value
11168+
);
11169+
11170+
let sql_unpivot_with_alias_and_multi_value_and_qualifier = concat!(
11171+
"SELECT * FROM sales AS s ",
11172+
"UNPIVOT INCLUDE NULLS ((first_quarter, second_quarter) ",
11173+
"FOR half_of_the_year IN (",
11174+
"(sales.Q1, sales.Q2) AS H1, ",
11175+
"(sales.Q3, sales.Q4) AS H2",
11176+
"))"
11177+
);
11178+
11179+
if let Unpivot { columns, .. } =
11180+
&verified_only_select(sql_unpivot_with_alias_and_multi_value_and_qualifier).from[0].relation
11181+
{
11182+
assert_eq!(
11183+
*columns,
11184+
vec![
11185+
ExprWithAlias {
11186+
expr: Expr::Tuple(vec![
11187+
Expr::CompoundIdentifier(vec![Ident::new("sales"), Ident::new("Q1"),]),
11188+
Expr::CompoundIdentifier(vec![Ident::new("sales"), Ident::new("Q2"),]),
11189+
]),
11190+
alias: Some(Ident::new("H1")),
11191+
},
11192+
ExprWithAlias {
11193+
expr: Expr::Tuple(vec![
11194+
Expr::CompoundIdentifier(vec![Ident::new("sales"), Ident::new("Q3"),]),
11195+
Expr::CompoundIdentifier(vec![Ident::new("sales"), Ident::new("Q4"),]),
11196+
]),
11197+
alias: Some(Ident::new("H2")),
11198+
},
11199+
]
11200+
);
11201+
}
11202+
11203+
assert_eq!(
11204+
verified_stmt(sql_unpivot_with_alias_and_multi_value_and_qualifier).to_string(),
11205+
sql_unpivot_with_alias_and_multi_value_and_qualifier
11206+
);
1109011207
}
1109111208

1109211209
#[test]
@@ -11184,20 +11301,14 @@ fn parse_pivot_unpivot_table() {
1118411301
index_hints: vec![],
1118511302
}),
1118611303
null_inclusion: None,
11187-
value: Ident {
11188-
value: "population".to_string(),
11189-
quote_style: None,
11190-
span: Span::empty()
11191-
},
11192-
11193-
name: Ident {
11194-
value: "year".to_string(),
11195-
quote_style: None,
11196-
span: Span::empty()
11197-
},
11304+
value: Expr::Identifier(Ident::new("population")),
11305+
name: Ident::new("year"),
1119811306
columns: ["population_2000", "population_2010"]
1119911307
.into_iter()
11200-
.map(Ident::new)
11308+
.map(|col| ExprWithAlias {
11309+
expr: Expr::Identifier(Ident::new(col)),
11310+
alias: None,
11311+
})
1120111312
.collect(),
1120211313
alias: Some(TableAlias {
1120311314
name: Ident::new("u"),

0 commit comments

Comments
 (0)