Skip to content

Commit 56848b0

Browse files
authored
feat: support multiple value for pivot (#1970)
1 parent 4b8797e commit 56848b0

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
@@ -1336,7 +1336,7 @@ pub enum TableFactor {
13361336
Pivot {
13371337
table: Box<TableFactor>,
13381338
aggregate_functions: Vec<ExprWithAlias>, // Function expression
1339-
value_column: Vec<Ident>,
1339+
value_column: Vec<Expr>,
13401340
value_source: PivotValueSource,
13411341
default_on_null: Option<Expr>,
13421342
alias: Option<TableAlias>,
@@ -2011,10 +2011,15 @@ impl fmt::Display for TableFactor {
20112011
} => {
20122012
write!(
20132013
f,
2014-
"{table} PIVOT({} FOR {} IN ({value_source})",
2014+
"{table} PIVOT({} FOR ",
20152015
display_comma_separated(aggregate_functions),
2016-
Expr::CompoundIdentifier(value_column.to_vec()),
20172016
)?;
2017+
if value_column.len() == 1 {
2018+
write!(f, "{}", value_column[0])?;
2019+
} else {
2020+
write!(f, "({})", display_comma_separated(value_column))?;
2021+
}
2022+
write!(f, " IN ({value_source})")?;
20182023
if let Some(expr) = default_on_null {
20192024
write!(f, " DEFAULT ON NULL ({expr})")?;
20202025
}

src/ast/spans.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2001,7 +2001,7 @@ impl Spanned for TableFactor {
20012001
} => union_spans(
20022002
core::iter::once(table.span())
20032003
.chain(aggregate_functions.iter().map(|i| i.span()))
2004-
.chain(value_column.iter().map(|i| i.span))
2004+
.chain(value_column.iter().map(|i| i.span()))
20052005
.chain(core::iter::once(value_source.span()))
20062006
.chain(default_on_null.as_ref().map(|i| i.span()))
20072007
.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
@@ -11067,6 +11067,18 @@ impl<'a> Parser<'a> {
1106711067
self.parse_parenthesized_column_list_inner(optional, allow_empty, |p| p.parse_identifier())
1106811068
}
1106911069

11070+
pub fn parse_parenthesized_compound_identifier_list(
11071+
&mut self,
11072+
optional: IsOptional,
11073+
allow_empty: bool,
11074+
) -> Result<Vec<Expr>, ParserError> {
11075+
self.parse_parenthesized_column_list_inner(optional, allow_empty, |p| {
11076+
Ok(Expr::CompoundIdentifier(
11077+
p.parse_period_separated(|p| p.parse_identifier())?,
11078+
))
11079+
})
11080+
}
11081+
1107011082
/// Parses a parenthesized comma-separated list of index columns, which can be arbitrary
1107111083
/// expressions with ordering information (and an opclass in some dialects).
1107211084
fn parse_parenthesized_index_column_list(&mut self) -> Result<Vec<IndexColumn>, ParserError> {
@@ -14187,7 +14199,13 @@ impl<'a> Parser<'a> {
1418714199
self.expect_token(&Token::LParen)?;
1418814200
let aggregate_functions = self.parse_comma_separated(Self::parse_aliased_function_call)?;
1418914201
self.expect_keyword_is(Keyword::FOR)?;
14190-
let value_column = self.parse_period_separated(|p| p.parse_identifier())?;
14202+
let value_column = if self.peek_token_ref().token == Token::LParen {
14203+
self.parse_parenthesized_column_list_inner(Mandatory, false, |p| {
14204+
p.parse_subexpr(self.dialect.prec_value(Precedence::Between))
14205+
})?
14206+
} else {
14207+
vec![self.parse_subexpr(self.dialect.prec_value(Precedence::Between))?]
14208+
};
1419114209
self.expect_keyword_is(Keyword::IN)?;
1419214210

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

tests/sqlparser_common.rs

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

1101311085
#[test]
@@ -11340,7 +11412,7 @@ fn parse_pivot_unpivot_table() {
1134011412
expr: call("sum", [Expr::Identifier(Ident::new("population"))]),
1134111413
alias: None
1134211414
}],
11343-
value_column: vec![Ident::new("year")],
11415+
value_column: vec![Expr::Identifier(Ident::new("year"))],
1134411416
value_source: PivotValueSource::List(vec![
1134511417
ExprWithAlias {
1134611418
expr: Expr::Value(

0 commit comments

Comments
 (0)