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
25 changes: 25 additions & 0 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,20 @@ pub enum TableFactor {
/// Optional alias for the resulting table.
alias: Option<TableAlias>,
},
/// Object unpivoting on a SUPER expression in the FROM clause.
///
/// Syntax:
/// ```sql
/// UNPIVOT expression AS value_alias [AT attribute_alias]
/// ```
UnpivotExpr {
/// SUPER expression to unpivot.
expression: Expr,
/// Alias for the generated unpivoted value.
value_alias: Ident,
/// Optional alias for the generated attribute key/index.
attribute_alias: Option<Ident>,
},
/// A `MATCH_RECOGNIZE` operation on a table.
///
/// See <https://docs.snowflake.com/en/sql-reference/constructs/match_recognize>.
Expand Down Expand Up @@ -2422,6 +2436,17 @@ impl fmt::Display for TableFactor {
}
Ok(())
}
TableFactor::UnpivotExpr {
expression,
value_alias,
attribute_alias,
} => {
write!(f, "UNPIVOT {expression} AS {value_alias}")?;
if let Some(attribute_alias) = attribute_alias {
write!(f, " AT {attribute_alias}")?;
}
Ok(())
}
TableFactor::MatchRecognize {
table,
partition_by,
Expand Down
9 changes: 9 additions & 0 deletions src/ast/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2044,6 +2044,15 @@ impl Spanned for TableFactor {
.chain(columns.iter().map(|ilist| ilist.span()))
.chain(alias.as_ref().map(|alias| alias.span())),
),
TableFactor::UnpivotExpr {
expression,
value_alias,
attribute_alias,
} => union_spans(
core::iter::once(expression.span())
.chain(core::iter::once(value_alias.span))
.chain(attribute_alias.as_ref().map(|alias| alias.span)),
),
TableFactor::MatchRecognize {
table,
partition_by,
Expand Down
10 changes: 10 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,16 @@ pub trait Dialect: Debug + Any {
false
}

/// Returns true if the dialect supports object-unpivot table factors in the FROM clause.
///
/// Syntax:
/// ```sql
/// UNPIVOT expression AS value_alias [AT attribute_alias]
/// ```
fn supports_unpivot_expr_in_from(&self) -> bool {
false
}

/// Returns true if the dialect supports the `CONSTRAINT` keyword without a name
/// in table constraint definitions.
///
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/redshift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ impl Dialect for RedshiftSqlDialect {
true
}

fn supports_unpivot_expr_in_from(&self) -> bool {
true
}

fn supports_string_escape_constant(&self) -> bool {
true
}
Expand Down
31 changes: 31 additions & 0 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16162,6 +16162,12 @@ impl<'a> Parser<'a> {
// `(mytable AS alias)`
alias.replace(outer_alias);
}
TableFactor::UnpivotExpr { .. } => {
return Err(ParserError::ParserError(
"alias after parenthesized UNPIVOT expression is not supported"
.to_string(),
))
}
};
}
// Do not store the extra set of parens in the AST
Expand Down Expand Up @@ -16243,6 +16249,10 @@ impl<'a> Parser<'a> {
with_offset_alias,
with_ordinality,
})
} else if self.dialect.supports_unpivot_expr_in_from()
&& self.parse_keyword(Keyword::UNPIVOT)
{
self.parse_unpivot_expr_table_factor()
} else if self.parse_keyword_with_tokens(Keyword::JSON_TABLE, &[Token::LParen]) {
let json_expr = self.parse_expr()?;
self.expect_token(&Token::Comma)?;
Expand Down Expand Up @@ -17241,6 +17251,27 @@ impl<'a> Parser<'a> {
})
}

/// Parse an object UNPIVOT table factor in FROM clause.
///
/// Syntax:
/// `UNPIVOT expression AS value_alias [AT attribute_alias]`
pub fn parse_unpivot_expr_table_factor(&mut self) -> Result<TableFactor, ParserError> {
let expression = self.parse_expr()?;
self.expect_keyword_is(Keyword::AS)?;
let value_alias = self.parse_identifier()?;
let attribute_alias = if self.parse_keyword(Keyword::AT) {
Some(self.parse_identifier()?)
} else {
None
};

Ok(TableFactor::UnpivotExpr {
expression,
value_alias,
attribute_alias,
})
}

/// Parse a JOIN constraint (`NATURAL`, `ON <expr>`, `USING (...)`, or no constraint).
pub fn parse_join_constraint(&mut self, natural: bool) -> Result<JoinConstraint, ParserError> {
if natural {
Expand Down
18 changes: 18 additions & 0 deletions tests/sqlparser_redshift.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,3 +542,21 @@ fn test_partiql_from_alias_with_at_index() {
_ => panic!("expected table factor"),
}
}

#[test]
fn parse_unpivot_expression() {
let sql = r#"SELECT t.id, k, v FROM test_colors as t, UNPIVOT t.count_by_color AS v AT k;
"#;

redshift().parse_sql_statements(sql).unwrap();

}

#[test]
fn parse_unpivot_no_brackets() {
let sql = r#"SELECT t.id, k, v FROM test_colors as t, UNPIVOT t AS v AT k;
"#;

redshift().parse_sql_statements(sql).unwrap();

}