Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,7 @@ pub enum TableFactor {
///
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#pivot_operator)
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/constructs/pivot)
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__GUID-68257B27-1C4C-4C47-8140-5C60E0E65D35)
Pivot {
/// The input table to pivot.
table: Box<TableFactor>,
Expand All @@ -1610,8 +1611,10 @@ pub enum TableFactor {
/// table UNPIVOT [ { INCLUDE | EXCLUDE } NULLS ] (value FOR name IN (column1, [ column2, ... ])) [ alias ]
/// ```
///
/// See <https://docs.snowflake.com/en/sql-reference/constructs/unpivot>.
/// See <https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot>.
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/constructs/unpivot)
/// [Databricks](https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-syntax-qry-select-unpivot)
/// [BigQuery](https://docs.cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#unpivot_operator)
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__GUID-9B4E0389-413C-4014-94A1-0A0571BDF7E1)
Unpivot {
/// The input table to unpivot.
table: Box<TableFactor>,
Expand Down
49 changes: 32 additions & 17 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13543,7 +13543,7 @@ impl<'a> Parser<'a> {
Keyword::PIVOT => {
self.expect_token(&Token::LParen)?;
let aggregate_functions =
self.parse_comma_separated(Self::parse_aliased_function_call)?;
self.parse_comma_separated(Self::parse_pivot_aggregate_function)?;
self.expect_keyword_is(Keyword::FOR)?;
let value_column = self.parse_period_separated(|p| p.parse_identifier())?;
self.expect_keyword_is(Keyword::IN)?;
Expand Down Expand Up @@ -16125,20 +16125,6 @@ impl<'a> Parser<'a> {
})
}

fn parse_aliased_function_call(&mut self) -> Result<ExprWithAlias, ParserError> {
let function_name = match self.next_token().token {
Token::Word(w) => Ok(w.value),
_ => self.expected("a function identifier", self.peek_token()),
}?;
let expr = self.parse_function(ObjectName::from(vec![Ident::new(function_name)]))?;
let alias = if self.parse_keyword(Keyword::AS) {
Some(self.parse_identifier()?)
} else {
None
};

Ok(ExprWithAlias { expr, alias })
}
/// Parses an expression with an optional alias
///
/// Examples:
Expand Down Expand Up @@ -16172,13 +16158,40 @@ impl<'a> Parser<'a> {
Ok(ExprWithAlias { expr, alias })
}

/// Parse an expression followed by an optional alias; Unlike
/// [Self::parse_expr_with_alias] the "AS" keyword between the expression
/// and the alias is optional.
fn parse_expr_with_alias_optional_as_keyword(&mut self) -> Result<ExprWithAlias, ParserError> {
let expr = self.parse_expr()?;
let alias = self.parse_identifier_optional_alias()?;
Ok(ExprWithAlias { expr, alias })
}

/// Parses a plain function call with an optional alias for the `PIVOT` clause
fn parse_pivot_aggregate_function(&mut self) -> Result<ExprWithAlias, ParserError> {
let function_name = match self.next_token().token {
Token::Word(w) => Ok(w.value),
_ => self.expected("a function identifier", self.peek_token()),
}?;
let expr = self.parse_function(ObjectName::from(vec![Ident::new(function_name)]))?;
let alias = {
fn validator(explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool {
// ~ for a PIVOT aggregate function the alias must not be a "FOR"; in any dialect
kw != &Keyword::FOR && parser.dialect.is_select_item_alias(explicit, kw, parser)
}
self.parse_optional_alias_inner(None, validator)?
};
Ok(ExprWithAlias { expr, alias })
}

/// Parse a PIVOT table factor (ClickHouse/Oracle style pivot), returning a TableFactor.
pub fn parse_pivot_table_factor(
&mut self,
table: TableFactor,
) -> Result<TableFactor, ParserError> {
self.expect_token(&Token::LParen)?;
let aggregate_functions = self.parse_comma_separated(Self::parse_aliased_function_call)?;
let aggregate_functions =
self.parse_comma_separated(Self::parse_pivot_aggregate_function)?;
self.expect_keyword_is(Keyword::FOR)?;
let value_column = if self.peek_token_ref().token == Token::LParen {
self.parse_parenthesized_column_list_inner(Mandatory, false, |p| {
Expand All @@ -16200,7 +16213,9 @@ impl<'a> Parser<'a> {
} else if self.peek_sub_query() {
PivotValueSource::Subquery(self.parse_query()?)
} else {
PivotValueSource::List(self.parse_comma_separated(Self::parse_expr_with_alias)?)
PivotValueSource::List(
self.parse_comma_separated(Self::parse_expr_with_alias_optional_as_keyword)?,
)
};
self.expect_token(&Token::RParen)?;

Expand Down
12 changes: 12 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11357,6 +11357,18 @@ fn parse_pivot_table() {
verified_stmt(multiple_value_columns_sql).to_string(),
multiple_value_columns_sql
);

// assert optional "AS" keyword for aliases for pivot values
one_statement_parses_to(
"SELECT * FROM t PIVOT(SUM(1) FOR a.abc IN (1 x, 'two' y, three z))",
"SELECT * FROM t PIVOT(SUM(1) FOR a.abc IN (1 AS x, 'two' AS y, three AS z))",
);

// assert optional "AS" keyword for aliases for pivot aggregate function
one_statement_parses_to(
"SELECT * FROM t PIVOT(SUM(1) x, COUNT(42) y FOR a.abc IN (1))",
"SELECT * FROM t PIVOT(SUM(1) AS x, COUNT(42) AS y FOR a.abc IN (1))",
);
}

#[test]
Expand Down