Skip to content

Commit e682430

Browse files
chenkovskyayman-sigma
authored andcommitted
feat: support multiple value for pivot (apache#1970)
1 parent ea4fffd commit e682430

File tree

5 files changed

+104
-7
lines changed

5 files changed

+104
-7
lines changed

src/ast/query.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,7 +1342,7 @@ pub enum TableFactor {
13421342
Pivot {
13431343
table: Box<TableFactor>,
13441344
aggregate_functions: Vec<ExprWithAlias>, // Function expression
1345-
value_column: Vec<Ident>,
1345+
value_column: Vec<Expr>,
13461346
value_source: PivotValueSource,
13471347
default_on_null: Option<Expr>,
13481348
alias: Option<TableAlias>,
@@ -2024,10 +2024,15 @@ impl fmt::Display for TableFactor {
20242024
} => {
20252025
write!(
20262026
f,
2027-
"{table} PIVOT({} FOR {} IN ({value_source})",
2027+
"{table} PIVOT({} FOR ",
20282028
display_comma_separated(aggregate_functions),
2029-
Expr::CompoundIdentifier(value_column.to_vec()),
20302029
)?;
2030+
if value_column.len() == 1 {
2031+
write!(f, "{}", value_column[0])?;
2032+
} else {
2033+
write!(f, "({})", display_comma_separated(value_column))?;
2034+
}
2035+
write!(f, " IN ({value_source})")?;
20312036
if let Some(expr) = default_on_null {
20322037
write!(f, " DEFAULT ON NULL ({expr})")?;
20332038
}

src/ast/spans.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2010,7 +2010,7 @@ impl Spanned for TableFactor {
20102010
} => union_spans(
20112011
core::iter::once(table.span())
20122012
.chain(aggregate_functions.iter().map(|i| i.span()))
2013-
.chain(value_column.iter().map(|i| i.span))
2013+
.chain(value_column.iter().map(|i| i.span()))
20142014
.chain(core::iter::once(value_source.span()))
20152015
.chain(default_on_null.as_ref().map(|i| i.span()))
20162016
.chain(alias.as_ref().map(|i| i.span())),

src/ast/visitor.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,8 @@ mod tests {
884884
"PRE: EXPR: a.amount",
885885
"POST: EXPR: a.amount",
886886
"POST: EXPR: SUM(a.amount)",
887+
"PRE: EXPR: a.MONTH",
888+
"POST: EXPR: a.MONTH",
887889
"PRE: EXPR: 'JAN'",
888890
"POST: EXPR: 'JAN'",
889891
"PRE: EXPR: 'FEB'",

src/parser/mod.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11098,6 +11098,18 @@ impl<'a> Parser<'a> {
1109811098
self.parse_parenthesized_column_list_inner(optional, allow_empty, |p| p.parse_identifier())
1109911099
}
1110011100

11101+
pub fn parse_parenthesized_compound_identifier_list(
11102+
&mut self,
11103+
optional: IsOptional,
11104+
allow_empty: bool,
11105+
) -> Result<Vec<Expr>, ParserError> {
11106+
self.parse_parenthesized_column_list_inner(optional, allow_empty, |p| {
11107+
Ok(Expr::CompoundIdentifier(
11108+
p.parse_period_separated(|p| p.parse_identifier())?,
11109+
))
11110+
})
11111+
}
11112+
1110111113
/// Parses a parenthesized comma-separated list of index columns, which can be arbitrary
1110211114
/// expressions with ordering information (and an opclass in some dialects).
1110311115
fn parse_parenthesized_index_column_list(&mut self) -> Result<Vec<IndexColumn>, ParserError> {
@@ -14219,7 +14231,13 @@ impl<'a> Parser<'a> {
1421914231
self.expect_token(&Token::LParen)?;
1422014232
let aggregate_functions = self.parse_comma_separated(Self::parse_aliased_function_call)?;
1422114233
self.expect_keyword_is(Keyword::FOR)?;
14222-
let value_column = self.parse_period_separated(|p| p.parse_identifier())?;
14234+
let value_column = if self.peek_token_ref().token == Token::LParen {
14235+
self.parse_parenthesized_column_list_inner(Mandatory, false, |p| {
14236+
p.parse_subexpr(self.dialect.prec_value(Precedence::Between))
14237+
})?
14238+
} else {
14239+
vec![self.parse_subexpr(self.dialect.prec_value(Precedence::Between))?]
14240+
};
1422314241
self.expect_keyword_is(Keyword::IN)?;
1422414242

1422514243
self.expect_token(&Token::LParen)?;

tests/sqlparser_common.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10964,7 +10964,10 @@ fn parse_pivot_table() {
1096410964
expected_function("b", Some("t")),
1096510965
expected_function("c", Some("u")),
1096610966
],
10967-
value_column: vec![Ident::new("a"), Ident::new("MONTH")],
10967+
value_column: vec![Expr::CompoundIdentifier(vec![
10968+
Ident::new("a"),
10969+
Ident::new("MONTH")
10970+
])],
1096810971
value_source: PivotValueSource::List(vec![
1096910972
ExprWithAlias {
1097010973
expr: Expr::value(number("1")),
@@ -11011,6 +11014,75 @@ fn parse_pivot_table() {
1101111014
verified_stmt(sql_without_table_alias).to_string(),
1101211015
sql_without_table_alias
1101311016
);
11017+
11018+
let multiple_value_columns_sql = concat!(
11019+
"SELECT * FROM person ",
11020+
"PIVOT(",
11021+
"SUM(age) AS a, AVG(class) AS c ",
11022+
"FOR (name, age) IN (('John', 30) AS c1, ('Mike', 40) AS c2))",
11023+
);
11024+
11025+
assert_eq!(
11026+
verified_only_select(multiple_value_columns_sql).from[0].relation,
11027+
Pivot {
11028+
table: Box::new(TableFactor::Table {
11029+
name: ObjectName::from(vec![Ident::new("person")]),
11030+
alias: None,
11031+
args: None,
11032+
with_hints: vec![],
11033+
version: None,
11034+
partitions: vec![],
11035+
with_ordinality: false,
11036+
json_path: None,
11037+
sample: None,
11038+
index_hints: vec![],
11039+
}),
11040+
aggregate_functions: vec![
11041+
ExprWithAlias {
11042+
expr: call("SUM", [Expr::Identifier(Ident::new("age"))]),
11043+
alias: Some(Ident::new("a"))
11044+
},
11045+
ExprWithAlias {
11046+
expr: call("AVG", [Expr::Identifier(Ident::new("class"))]),
11047+
alias: Some(Ident::new("c"))
11048+
},
11049+
],
11050+
value_column: vec![
11051+
Expr::Identifier(Ident::new("name")),
11052+
Expr::Identifier(Ident::new("age")),
11053+
],
11054+
value_source: PivotValueSource::List(vec![
11055+
ExprWithAlias {
11056+
expr: Expr::Tuple(vec![
11057+
Expr::Value(
11058+
(Value::SingleQuotedString("John".to_string())).with_empty_span()
11059+
),
11060+
Expr::Value(
11061+
(Value::Number("30".parse().unwrap(), false)).with_empty_span()
11062+
),
11063+
]),
11064+
alias: Some(Ident::new("c1"))
11065+
},
11066+
ExprWithAlias {
11067+
expr: Expr::Tuple(vec![
11068+
Expr::Value(
11069+
(Value::SingleQuotedString("Mike".to_string())).with_empty_span()
11070+
),
11071+
Expr::Value(
11072+
(Value::Number("40".parse().unwrap(), false)).with_empty_span()
11073+
),
11074+
]),
11075+
alias: Some(Ident::new("c2"))
11076+
},
11077+
]),
11078+
default_on_null: None,
11079+
alias: None,
11080+
}
11081+
);
11082+
assert_eq!(
11083+
verified_stmt(multiple_value_columns_sql).to_string(),
11084+
multiple_value_columns_sql
11085+
);
1101411086
}
1101511087

1101611088
#[test]
@@ -11343,7 +11415,7 @@ fn parse_pivot_unpivot_table() {
1134311415
expr: call("sum", [Expr::Identifier(Ident::new("population"))]),
1134411416
alias: None
1134511417
}],
11346-
value_column: vec![Ident::new("year")],
11418+
value_column: vec![Expr::Identifier(Ident::new("year"))],
1134711419
value_source: PivotValueSource::List(vec![
1134811420
ExprWithAlias {
1134911421
expr: Expr::Value(

0 commit comments

Comments
 (0)