Skip to content

Commit 8626051

Browse files
authored
Extend pivot operator support (apache#1238)
1 parent 4bfa399 commit 8626051

File tree

5 files changed

+140
-52
lines changed

5 files changed

+140
-52
lines changed

src/ast/mod.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ pub use self::dml::{Delete, Insert};
4141
pub use self::operator::{BinaryOperator, UnaryOperator};
4242
pub use self::query::{
4343
AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode,
44-
ExceptSelectItem, ExcludeSelectItem, Fetch, ForClause, ForJson, ForXml, GroupByExpr,
45-
IdentWithAlias, IlikeSelectItem, Join, JoinConstraint, JoinOperator, JsonTableColumn,
46-
JsonTableColumnErrorHandling, LateralView, LockClause, LockType, MatchRecognizePattern,
47-
MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset,
48-
OffsetRows, OrderByExpr, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement,
49-
ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, SetOperator,
50-
SetQuantifier, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion, TableWithJoins,
51-
Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With,
44+
ExceptSelectItem, ExcludeSelectItem, ExprWithAlias, Fetch, ForClause, ForJson, ForXml,
45+
GroupByExpr, IdentWithAlias, IlikeSelectItem, Join, JoinConstraint, JoinOperator,
46+
JsonTableColumn, JsonTableColumnErrorHandling, LateralView, LockClause, LockType,
47+
MatchRecognizePattern, MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr,
48+
NonBlock, Offset, OffsetRows, OrderByExpr, Query, RenameSelectItem, RepetitionQuantifier,
49+
ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr,
50+
SetOperator, SetQuantifier, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion,
51+
TableWithJoins, Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With,
5252
};
5353
pub use self::value::{
5454
escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString,

src/ast/query.rs

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,31 @@ impl fmt::Display for ConnectBy {
802802
}
803803
}
804804

805+
/// An expression optionally followed by an alias.
806+
///
807+
/// Example:
808+
/// ```sql
809+
/// 42 AS myint
810+
/// ```
811+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
812+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
813+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
814+
pub struct ExprWithAlias {
815+
pub expr: Expr,
816+
pub alias: Option<Ident>,
817+
}
818+
819+
impl fmt::Display for ExprWithAlias {
820+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
821+
let ExprWithAlias { expr, alias } = self;
822+
write!(f, "{expr}")?;
823+
if let Some(alias) = alias {
824+
write!(f, " AS {alias}")?;
825+
}
826+
Ok(())
827+
}
828+
}
829+
805830
/// A table name or a parenthesized subquery with an optional alias
806831
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
807832
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -900,12 +925,14 @@ pub enum TableFactor {
900925
},
901926
/// Represents PIVOT operation on a table.
902927
/// For example `FROM monthly_sales PIVOT(sum(amount) FOR MONTH IN ('JAN', 'FEB'))`
903-
/// See <https://docs.snowflake.com/en/sql-reference/constructs/pivot>
928+
///
929+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#pivot_operator)
930+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/constructs/pivot)
904931
Pivot {
905932
table: Box<TableFactor>,
906-
aggregate_function: Expr, // Function expression
933+
aggregate_functions: Vec<ExprWithAlias>, // Function expression
907934
value_column: Vec<Ident>,
908-
pivot_values: Vec<Value>,
935+
pivot_values: Vec<ExprWithAlias>,
909936
alias: Option<TableAlias>,
910937
},
911938
/// An UNPIVOT operation on a table.
@@ -1270,7 +1297,7 @@ impl fmt::Display for TableFactor {
12701297
}
12711298
TableFactor::Pivot {
12721299
table,
1273-
aggregate_function,
1300+
aggregate_functions,
12741301
value_column,
12751302
pivot_values,
12761303
alias,
@@ -1279,7 +1306,7 @@ impl fmt::Display for TableFactor {
12791306
f,
12801307
"{} PIVOT({} FOR {} IN ({}))",
12811308
table,
1282-
aggregate_function,
1309+
display_comma_separated(aggregate_functions),
12831310
Expr::CompoundIdentifier(value_column.to_vec()),
12841311
display_comma_separated(pivot_values)
12851312
)?;

src/ast/visitor.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,14 @@ mod tests {
850850
"PRE: EXPR: a.amount",
851851
"POST: EXPR: a.amount",
852852
"POST: EXPR: SUM(a.amount)",
853+
"PRE: EXPR: 'JAN'",
854+
"POST: EXPR: 'JAN'",
855+
"PRE: EXPR: 'FEB'",
856+
"POST: EXPR: 'FEB'",
857+
"PRE: EXPR: 'MAR'",
858+
"POST: EXPR: 'MAR'",
859+
"PRE: EXPR: 'APR'",
860+
"POST: EXPR: 'APR'",
853861
"POST: TABLE FACTOR: monthly_sales PIVOT(SUM(a.amount) FOR a.MONTH IN ('JAN', 'FEB', 'MAR', 'APR')) AS p (c, d)",
854862
"PRE: EXPR: EMPID",
855863
"POST: EXPR: EMPID",

src/parser/mod.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8775,27 +8775,49 @@ impl<'a> Parser<'a> {
87758775
})
87768776
}
87778777

8778+
fn parse_aliased_function_call(&mut self) -> Result<ExprWithAlias, ParserError> {
8779+
let function_name = match self.next_token().token {
8780+
Token::Word(w) => Ok(w.value),
8781+
_ => self.expected("a function identifier", self.peek_token()),
8782+
}?;
8783+
let expr = self.parse_function(ObjectName(vec![Ident::new(function_name)]))?;
8784+
let alias = if self.parse_keyword(Keyword::AS) {
8785+
Some(self.parse_identifier(false)?)
8786+
} else {
8787+
None
8788+
};
8789+
8790+
Ok(ExprWithAlias { expr, alias })
8791+
}
8792+
8793+
fn parse_expr_with_alias(&mut self) -> Result<ExprWithAlias, ParserError> {
8794+
let expr = self.parse_expr()?;
8795+
let alias = if self.parse_keyword(Keyword::AS) {
8796+
Some(self.parse_identifier(false)?)
8797+
} else {
8798+
None
8799+
};
8800+
8801+
Ok(ExprWithAlias { expr, alias })
8802+
}
8803+
87788804
pub fn parse_pivot_table_factor(
87798805
&mut self,
87808806
table: TableFactor,
87818807
) -> Result<TableFactor, ParserError> {
87828808
self.expect_token(&Token::LParen)?;
8783-
let function_name = match self.next_token().token {
8784-
Token::Word(w) => Ok(w.value),
8785-
_ => self.expected("an aggregate function name", self.peek_token()),
8786-
}?;
8787-
let function = self.parse_function(ObjectName(vec![Ident::new(function_name)]))?;
8809+
let aggregate_functions = self.parse_comma_separated(Self::parse_aliased_function_call)?;
87888810
self.expect_keyword(Keyword::FOR)?;
87898811
let value_column = self.parse_object_name(false)?.0;
87908812
self.expect_keyword(Keyword::IN)?;
87918813
self.expect_token(&Token::LParen)?;
8792-
let pivot_values = self.parse_comma_separated(Parser::parse_value)?;
8814+
let pivot_values = self.parse_comma_separated(Self::parse_expr_with_alias)?;
87938815
self.expect_token(&Token::RParen)?;
87948816
self.expect_token(&Token::RParen)?;
87958817
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
87968818
Ok(TableFactor::Pivot {
87978819
table: Box::new(table),
8798-
aggregate_function: function,
8820+
aggregate_functions,
87998821
value_column,
88008822
pivot_values,
88018823
alias,

tests/sqlparser_common.rs

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8453,11 +8453,32 @@ fn parse_escaped_string_without_unescape() {
84538453
#[test]
84548454
fn parse_pivot_table() {
84558455
let sql = concat!(
8456-
"SELECT * FROM monthly_sales AS a ",
8457-
"PIVOT(SUM(a.amount) FOR a.MONTH IN ('JAN', 'FEB', 'MAR', 'APR')) AS p (c, d) ",
8456+
"SELECT * FROM monthly_sales AS a PIVOT(",
8457+
"SUM(a.amount), ",
8458+
"SUM(b.amount) AS t, ",
8459+
"SUM(c.amount) AS u ",
8460+
"FOR a.MONTH IN (1 AS x, 'two', three AS y)) AS p (c, d) ",
84588461
"ORDER BY EMPID"
84598462
);
84608463

8464+
fn expected_function(table: &'static str, alias: Option<&'static str>) -> ExprWithAlias {
8465+
ExprWithAlias {
8466+
expr: Expr::Function(Function {
8467+
name: ObjectName(vec![Ident::new("SUM")]),
8468+
args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
8469+
Expr::CompoundIdentifier(vec![Ident::new(table), Ident::new("amount")]),
8470+
))]),
8471+
null_treatment: None,
8472+
filter: None,
8473+
over: None,
8474+
distinct: false,
8475+
special: false,
8476+
order_by: vec![],
8477+
}),
8478+
alias: alias.map(Ident::new),
8479+
}
8480+
}
8481+
84618482
assert_eq!(
84628483
verified_only_select(sql).from[0].relation,
84638484
Pivot {
@@ -8472,24 +8493,25 @@ fn parse_pivot_table() {
84728493
version: None,
84738494
partitions: vec![],
84748495
}),
8475-
aggregate_function: Expr::Function(Function {
8476-
name: ObjectName(vec![Ident::new("SUM")]),
8477-
args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
8478-
Expr::CompoundIdentifier(vec![Ident::new("a"), Ident::new("amount"),])
8479-
))]),
8480-
null_treatment: None,
8481-
filter: None,
8482-
over: None,
8483-
distinct: false,
8484-
special: false,
8485-
order_by: vec![],
8486-
}),
8496+
aggregate_functions: vec![
8497+
expected_function("a", None),
8498+
expected_function("b", Some("t")),
8499+
expected_function("c", Some("u")),
8500+
],
84878501
value_column: vec![Ident::new("a"), Ident::new("MONTH")],
84888502
pivot_values: vec![
8489-
Value::SingleQuotedString("JAN".to_string()),
8490-
Value::SingleQuotedString("FEB".to_string()),
8491-
Value::SingleQuotedString("MAR".to_string()),
8492-
Value::SingleQuotedString("APR".to_string()),
8503+
ExprWithAlias {
8504+
expr: Expr::Value(number("1")),
8505+
alias: Some(Ident::new("x"))
8506+
},
8507+
ExprWithAlias {
8508+
expr: Expr::Value(Value::SingleQuotedString("two".to_string())),
8509+
alias: None
8510+
},
8511+
ExprWithAlias {
8512+
expr: Expr::Identifier(Ident::new("three")),
8513+
alias: Some(Ident::new("y"))
8514+
},
84938515
],
84948516
alias: Some(TableAlias {
84958517
name: Ident {
@@ -8623,22 +8645,31 @@ fn parse_pivot_unpivot_table() {
86238645
columns: vec![]
86248646
}),
86258647
}),
8626-
aggregate_function: Expr::Function(Function {
8627-
name: ObjectName(vec![Ident::new("sum")]),
8628-
args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
8629-
Expr::Identifier(Ident::new("population"))
8630-
))]),
8631-
null_treatment: None,
8632-
filter: None,
8633-
over: None,
8634-
distinct: false,
8635-
special: false,
8636-
order_by: vec![],
8637-
}),
8648+
aggregate_functions: vec![ExprWithAlias {
8649+
expr: Expr::Function(Function {
8650+
name: ObjectName(vec![Ident::new("sum")]),
8651+
args: (vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(
8652+
Expr::Identifier(Ident::new("population"))
8653+
))]),
8654+
null_treatment: None,
8655+
filter: None,
8656+
over: None,
8657+
distinct: false,
8658+
special: false,
8659+
order_by: vec![],
8660+
}),
8661+
alias: None
8662+
}],
86388663
value_column: vec![Ident::new("year")],
86398664
pivot_values: vec![
8640-
Value::SingleQuotedString("population_2000".to_string()),
8641-
Value::SingleQuotedString("population_2010".to_string())
8665+
ExprWithAlias {
8666+
expr: Expr::Value(Value::SingleQuotedString("population_2000".to_string())),
8667+
alias: None
8668+
},
8669+
ExprWithAlias {
8670+
expr: Expr::Value(Value::SingleQuotedString("population_2010".to_string())),
8671+
alias: None
8672+
},
86428673
],
86438674
alias: Some(TableAlias {
86448675
name: Ident::new("p"),

0 commit comments

Comments
 (0)